UITableView tải nhiều hơn khi cuộn xuống dưới cùng như ứng dụng Facebook


95

Tôi đang phát triển một ứng dụng sử dụng SQLite. Tôi muốn hiển thị danh sách người dùng (UITableView) bằng cơ chế phân trang. Có ai vui lòng cho tôi biết cách tải thêm dữ liệu trong danh sách của tôi khi người dùng cuộn đến cuối danh sách (như trên trang chủ trên ứng dụng Facebook) không?

Câu trả lời:


103

Bạn có thể làm điều đó bằng cách thêm dấu kiểm xem bạn đang ở đâu trong cellForRowAtIndexPath:phương pháp. Phương pháp này rất dễ hiểu và dễ thực hiện:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Classic start method
    static NSString *cellIdentifier = @"MyCell";
    MyCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell)
    {
        cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MainMenuCellIdentifier];
    }

    MyData *data = [self.dataArray objectAtIndex:indexPath.row];
    // Do your cell customisation
    // cell.titleLabel.text = data.title;

    BOOL lastItemReached = [data isEqual:[[self.dataArray] lastObject]]; 
    if (!lastItemReached && indexPath.row == [self.dataArray count] - 1)
    {
        [self launchReload];
    }
}

EDIT: đã thêm một kiểm tra vào mục cuối cùng để ngăn chặn các cuộc gọi đệ quy. Bạn sẽ phải triển khai phương pháp xác định xem mục cuối cùng đã đến được hay chưa.

EDIT2: giải thích lastItemReached


9
Điều gì sẽ xảy ra nếu người dùng cuộn lên và xuống, vì vậy cellForRowAtIndexPath được gọi là NHIỀU LẦN! ??
onmyway133

Lần đầu tiên anh ấy cuộn đến cuối danh sách của anh ấy sẽ được tải lại. Và mỗi khi anh ta chạm đáy, một phần dữ liệu mới sẽ được thu thập. Nếu bất kỳ điều trị đặc hiệu đã được áp dụng, nó sẽ là launchReloadtrách nhiệm của phương pháp để xử lý nó (ví dụ, chỉ có một hành động tải lại không đồng bộ tại một thời điểm)
shinyuX

4
Tôi đã phải thêm một cờ để ngăn chặn sự cố đệ quy khi mục cuối cùng bị đánh trúng:if !lastItemReached && indexPath.row == dataArray!.hits.count - 1 {
Albert Bori

Là gì self.launchReloadphương pháp?
slider

1
@shinyuX không hoạt động đối với tôi, "nếu" luôn sai ... nhưng nếu (lastItemReached && indexPath.row == [self.dataArray count] - 1) đúng, TẠI SAO?
Cho biết

69

Nhanh

Phương pháp 1: Đã cuộn xuống dưới cùng

Đây là phiên bản Swift của câu trả lời của Pedro Romão . Khi người dùng ngừng cuộn, nó sẽ kiểm tra xem nó đã đến cuối chưa.

func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {

    // UITableView only moves in one direction, y axis
    let currentOffset = scrollView.contentOffset.y
    let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height

    // Change 10.0 to adjust the distance from bottom
    if maximumOffset - currentOffset <= 10.0 {
        self.loadMore()
    }
}

Phương pháp 2: Đã đến hàng cuối cùng

Và đây là phiên bản Swift của câu trả lời của glossuX . Nó kiểm tra xem người dùng đã đến hàng cuối cùng chưa.

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    // set up cell
    // ...

    // Check if the last row number is the same as the last current data element
    if indexPath.row == self.dataArray.count - 1 {
        self.loadMore()
    }

}

Ví dụ về một loadMore()phương pháp

Tôi thiết lập ba biến lớp này để tìm nạp các lô dữ liệu.

// number of items to be fetched each time (i.e., database LIMIT)
let itemsPerBatch = 50

// Where to start fetching items (database OFFSET)
var offset = 0

// a flag for when all database items have already been loaded
var reachedEndOfItems = false

Đây là chức năng để tải thêm các mục từ cơ sở dữ liệu vào dạng xem bảng.

func loadMore() {

    // don't bother doing another db query if already have everything
    guard !self.reachedEndOfItems else {
        return
    }

    // query the db on a background thread
    DispatchQueue.global(qos: .background).async {

        // determine the range of data items to fetch
        var thisBatchOfItems: [MyObjects]?
        let start = self.offset
        let end = self.offset + self.itemsPerBatch

        // query the database
        do {
            // SQLite.swift wrapper
            thisBatchOfItems = try MyDataHelper.findRange(start..<end)
        } catch _ {
            print("query failed")
        }

        // update UITableView with new batch of items on main thread after query finishes
        DispatchQueue.main.async {

            if let newItems = thisBatchOfItems {

                // append the new items to the data source for the table view
                self.myObjectArray.appendContentsOf(newItems)

                // reload the table view
                self.tableView.reloadData()

                // check if this was the last of the data
                if newItems.count < self.itemsPerBatch {
                    self.reachedEndOfItems = true
                    print("reached end of data. Batch count: \(newItems.count)")
                }

                // reset the offset for the next data query
                self.offset += self.itemsPerBatch
            }

        }
    }
}

Tôi đã sử dụng Phương pháp 1, vì tôi muốn kéo để tìm nạp thêm. Nó hoạt động tuyệt vời. Cảm ơn cả hai bạn!
Bob Wakefield

37

Tốt hơn nên sử dụng willDisplayCellphương pháp để kiểm tra xem ô nào sẽ được tải. Khi chúng ta có được dòng điện indexPath.rowcuối cùng, chúng ta có thể tải thêm các ô. Thao tác này sẽ tải nhiều ô hơn khi cuộn xuống.

 - (void)tableView:(UITableView *)tableView 
       willDisplayCell:(UITableViewCell *)cell    
       forRowAtIndexPath:(NSIndexPath *)indexPath
{
    // check if indexPath.row is last row
    // Perform operation to load new Cell's.
}

16
Nó không tốt hơn chút nào vì reloadData sẽ gọi lại phương thức này phải không?
Marcin

24

Chi tiết

  • Swift 5.1, Xcode 11.2.1

Giải pháp

Đã làm việc với UIScrollView / UICollectionView / UITableView

import UIKit

class LoadMoreActivityIndicator {

    private let spacingFromLastCell: CGFloat
    private let spacingFromLastCellWhenLoadMoreActionStart: CGFloat
    private weak var activityIndicatorView: UIActivityIndicatorView?
    private weak var scrollView: UIScrollView?

    private var defaultY: CGFloat {
        guard let height = scrollView?.contentSize.height else { return 0.0 }
        return height + spacingFromLastCell
    }

    deinit { activityIndicatorView?.removeFromSuperview() }

    init (scrollView: UIScrollView, spacingFromLastCell: CGFloat, spacingFromLastCellWhenLoadMoreActionStart: CGFloat) {
        self.scrollView = scrollView
        self.spacingFromLastCell = spacingFromLastCell
        self.spacingFromLastCellWhenLoadMoreActionStart = spacingFromLastCellWhenLoadMoreActionStart
        let size:CGFloat = 40
        let frame = CGRect(x: (scrollView.frame.width-size)/2, y: scrollView.contentSize.height + spacingFromLastCell, width: size, height: size)
        let activityIndicatorView = UIActivityIndicatorView(frame: frame)
        activityIndicatorView.color = .black
        activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
        activityIndicatorView.hidesWhenStopped = true
        scrollView.addSubview(activityIndicatorView)
        self.activityIndicatorView = activityIndicatorView
    }

    private var isHidden: Bool {
        guard let scrollView = scrollView else { return true }
        return scrollView.contentSize.height < scrollView.frame.size.height
    }

    func start(closure: (() -> Void)?) {
        guard let scrollView = scrollView, let activityIndicatorView = activityIndicatorView else { return }
        let offsetY = scrollView.contentOffset.y
        activityIndicatorView.isHidden = isHidden
        if !isHidden && offsetY >= 0 {
            let contentDelta = scrollView.contentSize.height - scrollView.frame.size.height
            let offsetDelta = offsetY - contentDelta

            let newY = defaultY-offsetDelta
            if newY < scrollView.frame.height {
                activityIndicatorView.frame.origin.y = newY
            } else {
                if activityIndicatorView.frame.origin.y != defaultY {
                    activityIndicatorView.frame.origin.y = defaultY
                }
            }

            if !activityIndicatorView.isAnimating {
                if offsetY > contentDelta && offsetDelta >= spacingFromLastCellWhenLoadMoreActionStart && !activityIndicatorView.isAnimating {
                    activityIndicatorView.startAnimating()
                    closure?()
                }
            }

            if scrollView.isDecelerating {
                if activityIndicatorView.isAnimating && scrollView.contentInset.bottom == 0 {
                    UIView.animate(withDuration: 0.3) { [weak self] in
                        if let bottom = self?.spacingFromLastCellWhenLoadMoreActionStart {
                            scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: bottom, right: 0)
                        }
                    }
                }
            }
        }
    }

    func stop(completion: (() -> Void)? = nil) {
        guard let scrollView = scrollView , let activityIndicatorView = activityIndicatorView else { return }
        let contentDelta = scrollView.contentSize.height - scrollView.frame.size.height
        let offsetDelta = scrollView.contentOffset.y - contentDelta
        if offsetDelta >= 0 {
            UIView.animate(withDuration: 0.3, animations: {
                scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
            }) { _ in completion?() }
        } else {
            scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
            completion?()
        }
        activityIndicatorView.stopAnimating()
    }
}

Sử dụng

trong đó

activityIndicator = LoadMoreActivityIndicator(scrollView: tableView, spacingFromLastCell: 10, spacingFromLastCellWhenLoadMoreActionStart: 60)

sự điều khiển

extension ViewController: UITableViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        activityIndicator.start {
            DispatchQueue.global(qos: .utility).async {
                sleep(3)
                DispatchQueue.main.async { [weak self] in
                    self?.activityIndicator.stop()
                }
            }
        }
    }
}

Đầy đủ mẫu

Đừng quên dán mã giải pháp.

import UIKit

class ViewController: UIViewController {

    fileprivate var activityIndicator: LoadMoreActivityIndicator!

    override func viewDidLoad() {
        super.viewDidLoad()
        let tableView = UITableView(frame: view.frame)
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true

        tableView.dataSource = self
        tableView.delegate = self
        tableView.tableFooterView = UIView()
        activityIndicator = LoadMoreActivityIndicator(scrollView: tableView, spacingFromLastCell: 10, spacingFromLastCellWhenLoadMoreActionStart: 60)
    }
}

extension ViewController: UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 30
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = "\(indexPath)"
        return cell
    }
}

extension ViewController: UITableViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        activityIndicator.start {
            DispatchQueue.global(qos: .utility).async {
                for i in 0..<3 {
                    print("!!!!!!!!! \(i)")
                    sleep(1)
                }
                DispatchQueue.main.async { [weak self] in
                    self?.activityIndicator.stop()
                }
            }
        }
    }
}

Kết quả

nhập mô tả hình ảnh ở đây


Hoạt động hoàn hảo. Nhưng tôi có tiêu đề trong chế độ xem bảng của mình, sau khi kéo để tải thêm, tiêu đề sẽ nằm dưới thanh điều hướng .. UIEdgeInsetsMake in loadMoreActionFinshed phải được đặt thành (62, 0, 0, 0) xem xét 66 = navbar.height + 22
Desmond

Nó sẽ hoạt động trong CollectionView khi bạn cuộn theo chiều dọc.
Vasily Bodnarchuk

Không thể tin được ... Tuyệt!
Tà Truhoada

bất kỳ phiên bản mục tiêu-c này?
Syed Ali Salman

@VasilyBodnarchuk không vấn đề gì, tôi sẽ làm điều đó và chia sẻ ở đây cho những người khác
Syed Ali Salman

18
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    NSInteger lastSectionIndex = [tableView numberOfSections] - 1;
    NSInteger lastRowIndex = [tableView numberOfRowsInSection:lastSectionIndex] - 1;
    if ((indexPath.section == lastSectionIndex) && (indexPath.row == lastRowIndex)) {
        // This is the last cell
        [self loadMore];
    }
}

Nếu bạn đang sử dụng Dữ liệu cốt lõi và NSFetchedResultsController, thì loadMorecó thể giống như sau:

// Load more
- (void)loadMore {
    [self.fetchedResultsController.fetchRequest setFetchLimit:newFetchLimit];
    [NSFetchedResultsController deleteCacheWithName:@"cache name"];
    NSError *error;
    if (![self.fetchedResultsController performFetch:&error]) {
        // Update to handle the error appropriately.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    }

    [self.tableView reloadData];
}

Tôi đang cố gắng thực hiện điều này nhưng tôi đang sử dụng một mảng kết quả chứ không phải sqlite, tôi đã tự hỏi làm thế nào để thêm nhiều hơn vào NSMutableArray hiện tại mà tôi có và sau đó tải lại dữ liệu, vì nếu không dữ liệu sẽ bị ghi đè ... Tôi đã thử this [tên addObjectsFromArray: [giá trị responseObjectForKeyPath: @ "name"]]; nhưng nó không hoạt động ... đây là liên kết đến câu hỏi của tôi stackoverflow.com/questions/23446780/…
Lion789

1
Mục đích để tìm nạp lại dữ liệu mỗi khi bạn nhận được dữ liệu mới là gì? Nếu frc được định cấu hình đúng, chỉ tìm nạp một lần là đủ, nó sẽ cập nhật tương ứng khi cần. Tìm nạp nó mọi lúc, giả sử yêu cầu tìm nạp của frc được định cấu hình thành ngữ cảnh luồng chính sẽ chặn luồng chính khi nó chạm vào đĩa, điều đó hoàn toàn không tốt cho trải nghiệm người dùng khi người dùng muốn có dữ liệu mới.
MANIAK_dobrii

Nửa đầu của điều này rất hữu ích cho tôi, cảm ơn. (Không sử dụng FetchedResultsVC)
weienw

@MANIAK_dobrii là đúng. Một trong những tính năng chính của NSFetchedResultsController là nó tính toán dữ liệu phân trang để bạn có thể cuộn ảo miễn phí khi bạn kết nối nó với UITableView. Việc triển khai một hàm loadMore như vậy chỉ cần thiết nếu bạn thực sự lấp đầy kho CoreData của mình với nhiều dữ liệu hơn, trong trường hợp đó không cần thực hiện một hàm PerformFetch khác nếu NSFetchedResultsController của bạn được định cấu hình chính xác.
Ali Gangji

Các vấn đề tương tự như các câu trả lời khác. reloadData khiến điều này xảy ra nhiều lần.
dyson trả về

11

Tôi đã triển khai một giải pháp mà tôi tìm thấy trong stackoverflow và nó hoạt động tốt, nhưng tôi nghĩ rằng giải pháp của glossuX rất dễ thực hiện và hoạt động tốt cho đề xuất của tôi. Nếu ai đó muốn một giải pháp khác có thể sử dụng giải pháp này bên dưới.

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{

   // UITableView only moves in one direction, y axis
    CGFloat currentOffset = scrollView.contentOffset.y;
    CGFloat maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;

    //NSInteger result = maximumOffset - currentOffset;

    // Change 10.0 to adjust the distance from bottom
    if (maximumOffset - currentOffset <= 10.0) {
        [self loadOneMorePage];
        //[self methodThatAddsDataAndReloadsTableView];
    }
}

Tôi nghĩ rằng có kịch bản khác nhau để xem trình bày, trong dung dịch trường hợp của bạn tôi làm việc, tôi cần một cái gì đó như thế này
Raheel Sadiq

Nếu người dùng ném mạnh, tức là cao 1,5 màn hình, có thể chạm đến đáy mà không kích hoạt làm mới.
dyson trả về

nhưng nó cuộn danh sách lên đầu
Mansuu ....

7

Chi tiết

  • Swift 5.1, Xcode 11.3.1

Giải pháp

Phần mở rộng UITableView di truyền cho Loadmore.

thêm phần mở rộng UITableView + này vào tệp mới của bạn

extension UITableView{

    func indicatorView() -> UIActivityIndicatorView{
        var activityIndicatorView = UIActivityIndicatorView()
        if self.tableFooterView == nil{
            let indicatorFrame = CGRect(x: 0, y: 0, width: self.bounds.width, height: 40)
            activityIndicatorView = UIActivityIndicatorView(frame: indicatorFrame)
            activityIndicatorView.isHidden = false
            activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
            activityIndicatorView.isHidden = true
            self.tableFooterView = activityIndicatorView
            return activityIndicatorView
        }else{
            return activityIndicatorView
        }
    }

    func addLoading(_ indexPath:IndexPath, closure: @escaping (() -> Void)){
        indicatorView().startAnimating()
        if let lastVisibleIndexPath = self.indexPathsForVisibleRows?.last {
            if indexPath == lastVisibleIndexPath && indexPath.row == self.numberOfRows(inSection: 0) - 1 {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                    closure()
                }
            }
        }
        indicatorView().isHidden = false
    }

    func stopLoading(){
        indicatorView().stopAnimating()
        indicatorView().isHidden = true
    }
}

Bây giờ, chỉ cần thêm dòng mã sau vào UITableViewDelegate Method willDisplay Cell trong ViewController của bạn và đảm bảo tableView.delegate = self

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    // need to pass your indexpath then it showing your indicator at bottom 
    tableView.addLoading(indexPath) {
        // add your code here
        // append Your array and reload your tableview
        tableView.stopLoading() // stop your indicator
    }
}

Kết quả

nhập mô tả hình ảnh ở đây

Đó là nó .. Hy vọng điều này hữu ích. Cảm ơn bạn


Những điều cần cân nhắc. Chỉ cần thêm 'tableFooterView = nil' vào bên trong func dừng tải, nếu không chỉ báo quay sẽ không ngừng hoạt ảnh. Ngoài ra, có thuộc tính trong activityIndicator 'hidesWhenStopped', vì vậy bạn không cần phải đặt chỉ báo true / false ẩn theo cách thủ công. Nhưng nhìn chung nó trông rất tuyệt :)
zramled

1
Cảm ơn gợi ý, tôi sẽ kiểm tra một lần và chỉnh sửa câu trả lời này :-)
Yogesh Patel

6

Sử dụng giới hạn và bù đắp trong các truy vấn của bạn và lấp đầy chế độ xem bảng của bạn với nội dung đó. Khi người dùng cuộn xuống, hãy tải phần bù tiếp theo.

Triển khai tableView:willDisplayCell:forRowAtIndexPath:phương thức trong của bạn UITableViewDelegatevà kiểm tra xem đó có phải là hàng cuối cùng không


5

Liên kết dưới đây sẽ cung cấp mã mẫu. # Swift3

Người dùng cần kéo lên ô xem bảng cuối cùng, ít nhất là 2 ô để tìm nạp thêm dữ liệu từ máy chủ.

Bạn cũng sẽ tìm thấy ô Quy trình để hiển thị quá trình tải như trong ô cuối cùng.

Nó trong Swift3

https://github.com/yogendrabagoriya/YBTableViewPullData


3

Thêm một tùy chọn để sử dụng ( Swift 3 và iOS 10+):

class DocumentEventsTableViewController: UITableViewController, UITableViewDataSourcePrefetching {

     var currentPage: Int = 1
     let pageSize: Int = 10 // num of items in one page

     override func viewDidLoad() {
         super.viewDidLoad()

         self.tableView.prefetchDataSource = self
     }

     func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
         let upcomingRows = indexPaths.map { $0.row }

         if let maxIndex = upcomingRows.max() {

            let nextPage: Int = Int(ceil(Double(maxIndex) / Double(pageSize))) + 1

            if nextPage > currentPage {
                 // Your function, which attempts to load respective page from the local database
                 loadLocalData(page: nextPage)

                 // Your function, which makes a network request to fetch the respective page of data from the network
                 startLoadingDataFromNetwork(page: nextPage) 

                 currentPage = nextPage
             }
         }
     }
 }

Đối với các trang khá nhỏ (~ 10 mục), bạn có thể muốn thêm dữ liệu cho các trang 1 và 2 theo cách thủ công vì nextPage có thể ở đâu đó khoảng 1-2 cho đến khi bảng có một vài mục được cuộn tốt. Nhưng nó sẽ hoạt động tốt cho tất cả các trang tiếp theo.


1
Điều này chỉ hoạt động đối với dữ liệu chỉ đọc. Không hoạt động Nếu bạn có chức năng như xóa một số hàng và tải thêm vì pageSize đã được sửa ở đây và không thể tải thêm nữa Nếu có nhiều dữ liệu hơn sau khi bạn cập nhật nguồn của mình.
EI Captain v2.0

2
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    if (news.count == 0) {
        return 0;
    } else {
        return news.count +  1 ;
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    @try {

        uint position = (uint) (indexPath.row);
        NSUInteger row = [indexPath row];
        NSUInteger count = [news count];

        //show Load More
        if (row == count) {
            UITableViewCell *cell = nil;

            static NSString *LoadMoreId = @"LoadMore";
            cell = [tableView dequeueReusableCellWithIdentifier:LoadMoreId];
            if (cell == nil) {
                cell = [[UITableViewCell alloc]
                        initWithStyle:UITableViewCellStyleDefault
                      reuseIdentifier:LoadMoreId];
            }
            if (!hasMoreLoad) {
                cell.hidden = true;
            } else {

                cell.textLabel.text = @"Load more items...";
                cell.textLabel.textColor = [UIColor blueColor];
                cell.textLabel.font = [UIFont boldSystemFontOfSize:14];
                NSLog(@"Load more");
                if (!isMoreLoaded) {
                    isMoreLoaded = true;
                    [self performSelector:@selector(loadMoreNews) withObject:nil afterDelay:0.1];
                }
            }

            return cell;

        } else {
            NewsRow *cell = nil;

            NewsObject *newsObject = news[position];
            static NSString *CellIdentifier = @"NewsRow";
            cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

            if (cell == nil) {
                // Load the top-level objects from the custom cell XIB.
                NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil];
                // Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
                cell = topLevelObjects[0];
                // Configure the cell...

            }

            cell.title.text = newsObject.title;             
            return cell;
        }

    }
    @catch (NSException *exception) {
        NSLog(@"Exception occurred: %@, %@", exception, [exception userInfo]);
    }
    return nil;
}

giải thích rất tốt về bài đăng này.

http://useyourloaf.com/blog/2010/10/02/dynamently-loading-new-rows-into-a-table.html

đơn giản, bạn phải thêm hàng cuối cùng và ẩn nó đi và khi hàng trong bảng nhấn hàng cuối cùng hơn là hiển thị hàng và tải nhiều mục hơn.


1

bạn nên kiểm tra ios UITableViewDataSourcePrefetching.

class ViewController: UIViewController {
    @IBOutlet weak var mytableview: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        mytableview.prefetchDataSource = self
    }

 func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
        print("prefetchdRowsAtIndexpath \(indexPaths)")
    }

    func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
        print("cancelPrefetchingForRowsAtIndexpath \(indexPaths)")
    }


}

1

để tải từ một API, Nó hoạt động đối với tôi, Xcode 10 , nhanh chóng 4.2 :

1- tạo tệp Swift mới và làm như sau:

//
//  apiTVCController.swift
//  ApiTestingTableView
//
//  Created by Hooma7n on 4/7/19.
//  Copyright © 2019 Hooma7n. All rights reserved.
//

import Foundation
import Alamofire

class apiget {

    var tableData : [Datum] = []
    var loadin : [Datum] = []
    var testfortotal : Int?


    func getfromapi(completionHandler : ((_ isSucess : Bool) -> Void)?) {
        let url = "https://reqres.in/api/users?page=1"
        Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil)
            .responseJSON(completionHandler : { response in
                switch response.result {
                case .success(let data):
                    guard let jsonData = try? JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions.prettyPrinted) else {return}
                    let decoder = JSONDecoder()
                    guard let result = try? decoder.decode(Welcome.self, from: jsonData) else {return}
                    self.tableData = result.data ?? []
                    self.testfortotal = result.total ?? 0
                    completionHandler?(true)

                //                    print(result)
                case .failure(let error):
                    print(error)
                }
            })
    }

    var pagecounter : Int = 2


    func loadmore(completionHandler : ((_ isSucess : Bool) -> Void)?){

        let url = "https://reqres.in/api/users?page=\(pagecounter)"
        Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil)
            .responseJSON(completionHandler : { response in
                switch response.result {
                case .success(let data):
                    guard let jsonData = try? JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions.prettyPrinted) else {return}
                    let decoder = JSONDecoder()
                    guard let myresult = try? decoder.decode(Welcome.self, from: jsonData) else {return}
                    self.loadin = myresult.data ?? []
                    self.tableData.append(contentsOf: myresult.data ?? [])
                    completionHandler?(true)
                    print(self.pagecounter)
                    self.pagecounter += 1

                //                    print(myresult)
                case .failure(let error):
                    print(error)
                }
            })

    }

}

extension apiget {

    struct Welcome: Codable {
        let page, perPage, total, totalPages: Int?
        var data: [Datum]?

        enum CodingKeys: String, CodingKey {
            case page
            case perPage = "per_page"
            case total
            case totalPages = "total_pages"
            case data
        }
    }

    struct Datum: Codable {
        let id: Int?
        let firstName, lastName: String?
        let avatar: String?

        enum CodingKeys: String, CodingKey {
            case id
            case firstName = "first_name"
            case lastName = "last_name"
            case avatar
        }
    }


}

2- trong tệp ViewController của bạn (Bộ điều khiển tableView):

//
//  apiTVC.swift
//  ApiTestingTableView
//
//  Created by Hooma7n on 4/7/19.
//  Copyright © 2019 Hooma7n. All rights reserved.
//

import UIKit
import Alamofire

class apiTVC: UITableViewController {

    var datamodel = apiget()

    override func viewDidLoad() {
        super.viewDidLoad()

        datamodel.getfromapi(completionHandler: {finish in
            if finish {self.tableView.reloadData()
            }

        })

    }


    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return datamodel.tableData.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! apiTableViewCell
        cell.firstNameLabel.text = datamodel.tableData[indexPath.row].firstName
        cell.lastNameLabel.text = datamodel.tableData[indexPath.row].lastName
        cell.dateLabel.text = "\(datamodel.tableData[indexPath.row].id ?? 0)"
        cell.profileImageView.loadImage(fromURL: datamodel.tableData[indexPath.row].avatar ?? "")

        return cell

    }

    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        let lastElement = datamodel.tableData.count - 1
        let total = datamodel.testfortotal ?? 12
        if indexPath.row == lastElement && datamodel.tableData.count < total{

            datamodel.loadmore(completionHandler: {finish in
                if finish {

                    self.tableView.reloadData()

                }})
        }
    }
}

nếu sử dụng tableView trong bộ ủy nhiệm viewController của bạn , nguồn dữ liệu tự có trong viewDidLoad.


0

Chỉ muốn chia sẻ cách tiếp cận này:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    NSLog(@"%@", [[YourTableView indexPathsForVisibleRows] lastObject]);
    [self estimatedTotalData];
}

- (void)estimatedTotalData
{
    long currentRow = ((NSIndexPath *)[[YourTableView indexPathsForVisibleRows] lastObject]).row;

    long estimateDataCount = 25;

    while (currentRow > estimateDataCount)
    {
        estimateDataCount+=25;
    }

    dataLimit = estimateDataCount;

    if (dataLimit == currentRow+1)
    {
        dataLimit+=25;
    }

    NSLog(@"dataLimit :%ld", dataLimit);

    [self requestForData];

    // this answers the question..
    //
    if(YourDataSource.count-1 == currentRow)
    {
        NSLog(@"LAST ROW"); //loadMore data
    }
}

NSLog(...); đầu ra sẽ giống như:

<NSIndexPath: 0xc0000000002e0016> {length = 2, path = 0 - 92}
dataLimit :100
<NSIndexPath: 0xc000000000298016> {length = 2, path = 0 - 83}
dataLimit :100
<NSIndexPath: 0xc000000000278016> {length = 2, path = 0 - 79}
dataLimit :100
<NSIndexPath: 0xc000000000238016> {length = 2, path = 0 - 71}
dataLimit :75
<NSIndexPath: 0xc0000000001d8016> {length = 2, path = 0 - 59}
dataLimit :75
<NSIndexPath: 0xc0000000001c0016> {length = 2, path = 0 - 56}
dataLimit :75
<NSIndexPath: 0xc000000000138016> {length = 2, path = 0 - 39}
dataLimit :50
<NSIndexPath: 0xc000000000120016> {length = 2, path = 0 - 36}
dataLimit :50
<NSIndexPath: 0xc000000000008016> {length = 2, path = 0 - 1}
dataLimit :25
<NSIndexPath: 0xc000000000008016> {length = 2, path = 0 - 1}
dataLimit :25

Điều này tốt cho việc hiển thị dữ liệu được lưu trữ cục bộ. Ban đầu tôi khai báo dataLimit là 25, có nghĩa là uitableview sẽ có 0-24 (ban đầu).

Nếu người dùng cuộn xuống dưới cùng và ô cuối cùng hiển thị dataLimitsẽ được thêm 25 ...

Lưu ý: Đây giống như một phân trang dữ liệu UITableView, :)


0
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

NSInteger sectionsAmount = [tableView numberOfSections];
NSInteger rowsAmount = [tableView numberOfRowsInSection:[indexPath section]];
if ([indexPath section] == sectionsAmount - 1 && [indexPath row] == rowsAmount - 1) {
    //get last row
    if (!isSearchActive && !isFilterSearchActive) {
        if (totalRecords % 8 == 0) {
            int64_t delayInSeconds = 2.0;
            dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
            dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {


            [yourTableView beginUpdates];
            [yourTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
            [yourTableView endUpdates];
            });
        }
    }
}
}

sau khi hiển thị hàng cuối cùng, hãy chèn các hàng tức là beginUpdates..và sử dụng một số độ trễ để không gặp sự cố.
Sahila Mirajkar

0

Cách tốt nhất để giải quyết vấn đề này là thêm ô ở cuối bảng của bạn và ô này sẽ giữ chỉ báo.

Trong thời gian nhanh chóng, bạn cần thêm điều này:

  1. Tạo ô mới thuộc loại ô Điền vào thao tác này sẽ giữ chỉ báo. Nhìn vào đoạn mã dưới đây
  2. Nhìn vào số hàng và thêm 1 vào nó (Đây là để tải ô).
  3. bạn cần kiểm tra trong rawAtIndex nếu idexPath.row == yourArray.count rồi trả về Đang tải ô.

nhìn vào mã bên dưới:

import UIKit

class LoadingCell: UITableViewCell {

@IBOutlet weak var indicator: UIActivityIndicatorView!


}

Đối với chế độ xem bảng: numOfRows:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return  yourArray.count + 1
}

cellForRawAt indexPath:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    if indexPath.row == users.count  {
        // need to change
        let loading = Bundle.main.loadNibNamed("LoadingCell", owner: LoadingCell.self , options: nil)?.first as! LoadingCell
        return loading

    }

    let yourCell = tableView.dequeueReusableCell(withIdentifier: "cellCustomizing", for: indexPath) as! UITableViewCell

    return yourCell

}

Nếu bạn nhận thấy rằng ô tải của tôi được tạo từ một tệp nib. Video này sẽ giải thích những gì tôi đã làm.


0
let threshold = 100.0 // threshold from bottom of tableView
var isLoadingMore = false // flag


func scrollViewDidScroll(scrollView: UIScrollView) {
    let contentOffset = scrollView.contentOffset.y
    let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;

    if !isLoadingMore && (maximumOffset - contentOffset <= threshold) {
        // Get more data - API call
        self.isLoadingMore = true

        // Update UI
        dispatch_async(dispatch_get_main_queue()) {
            tableView.reloadData()
            self.isLoadingMore = false
        }
    }
  }

0

Đối với Xcode 10.1, Swift 4.2

Video này có vẻ giống như một hướng dẫn tuyệt vời!

Starter / Complete project: https://github.com/RobCanton/Swift-Infinite-Scrolling-Example

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    var tableView:UITableView!

    var fetchingMore = false
    var items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        initTableView()
    }

    func initTableView() {
        tableView = UITableView(frame: view.bounds, style: .plain)
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "tableCell")
        tableView.delegate = self
        tableView.dataSource = self

        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false

        let layoutGuide = view.safeAreaLayoutGuide
        tableView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
        tableView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true

        tableView.reloadData()
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
            cell.textLabel?.text = "Item \(items[indexPath.row])"
            return cell
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offsetY = scrollView.contentOffset.y
        let contentHeight = scrollView.contentSize.height

        if offsetY > contentHeight - scrollView.frame.height * 4 {
            if !fetchingMore {
                beginBatchFetch()
            }
        }
    }

    func beginBatchFetch() {
        fetchingMore = true
        print("Call API here..")
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.50, execute: {
            print("Consider this as API response.")
            let newItems = (self.items.count...self.items.count + 12).map { index in index }
            self.items.append(contentsOf: newItems)
            self.fetchingMore = false
            self.tableView.reloadData()
        })
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.