UITableView Facebook uygulaması gibi aşağı kaydırırken daha fazla yükle


96

SQLite kullanan bir uygulama geliştiriyorum. Sayfalandırma mekanizması kullanarak bir kullanıcı listesi (UITableView) göstermek istiyorum. Herhangi biri, kullanıcı listenin sonuna geldiğinde listeme nasıl daha fazla veri yükleyeceğimi söyleyebilir mi (Facebook uygulamasındaki ana sayfada olduğu gibi)?

Yanıtlar:


103

cellForRowAtIndexPath:Yöntemde nerede olduğunuzu kontrol ederek bunu yapabilirsiniz . Bu yöntemin anlaşılması ve uygulanması kolaydır:

- (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];
    }
}

DÜZENLEME: özyineleme çağrılarını önlemek için son öğeye bir kontrol eklendi. Son öğeye ulaşılıp ulaşılmadığını tanımlayan yöntemi uygulamanız gerekecektir.

EDIT2: lastItemReached açıklandı


9
Ya kullanıcı yukarı ve aşağı kaydırırsa, hücreForRowAtIndexPath'e MANY TIMES denir! ??
onmyway133

İlk kez aşağı kaydırdığında listesi yeniden yüklenecek. Ve her seferinde dibe vurduğunda yeni bir veri yığını toplanacak. Herhangi bir özel işlemin uygulanması gerekiyorsa, onu launchReloadele almak yöntemin sorumluluğunda olacaktır (örneğin, bir seferde yalnızca bir eşzamansız yeniden yükleme eylemi)
shinyuX

4
Son öğe vurulduğunda bir yineleme sorununu önlemek için bir bayrak eklemem gerekiyordu:if !lastItemReached && indexPath.row == dataArray!.hits.count - 1 {
Albert Bori

Nedir self.launchReloadyöntemi?
slider

1
@shinyuX benim için çalışmıyor, "eğer" her zaman yanlış ... ama eğer (lastItemReached && indexPath.row == [self.dataArray sayımı] - 1) true, NEDEN?
Bahsedilen

69

Swift

Yöntem 1: En alta kaydırıldı

Pedro Romão'nun cevabının Swift versiyonu burada . Kullanıcı kaydırmayı bıraktığında, aşağıya ulaşıp ulaşmadığını kontrol eder.

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()
    }
}

Yöntem 2: Son satıra ulaşıldı

Ve işte shinyuX'un cevabının Swift versiyonu . Kullanıcının son satıra ulaşıp ulaşmadığını kontrol eder.

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()
    }

}

Bir loadMore()yöntem örneği

Bu üç sınıf değişkenini veri yığınlarını almak için ayarladım.

// 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

Bu, veritabanından tablo görünümüne daha fazla öğe yükleme işlevidir.

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
            }

        }
    }
}

Yöntem 1'i kullandım, çünkü daha fazlasını almak için çekmek istedim. Harika çalışıyor. İkinize de teşekkürler!
Bob Wakefield

37

willDisplayCellHangi hücrenin yüklenip yüklenmeyeceğini kontrol etmek için yöntemi kullanmak daha iyidir. Akımı indexPath.rowen son aldığımızda daha fazla hücre yükleyebiliriz. Bu, aşağı kaydırıldığında daha fazla hücre yükleyecektir.

 - (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
reloadData bu yöntemi tekrar çağıracağından daha iyi değil mi?
Marcin

Bölümlerimiz varsa bu işe yarar mı?
Abdul Yasin

Evet, bu bölümler için çalışacaktır, indexPath size hem satır hem de bölüm verecektir.
Suraj Mirajkar

24

Detaylar

  • Swift 5.1, Xcode 11.2.1

Çözüm

UIScrollView / UICollectionView / UITableView ile çalıştı

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()
    }
}

Kullanım

içinde

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

kullanım

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()
                }
            }
        }
    }
}

Tam Örnek

Çözüm kodunu yapıştırmayı unutmayınız.

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()
                }
            }
        }
    }
}

Sonuç

görüntü açıklamasını buraya girin


Mükemmel çalışıyor. Ancak tablo görünümümde bir başlığım var, daha fazla yüklemek için sürükledikten sonra başlık gezinme çubuğunun altına girecek .. UIEdgeInsetsMake in loadMoreActionFinshed, 66 = navbar.height + 22 dikkate alınarak (62, 0, 0, 0) olarak ayarlanmalıdır
Desmond

Dikey olarak kaydırdığınızda CollectionView'da çalışmalıdır.
Vasily Bodnarchuk

İnanılmaz ... Harika!
Tà Truhoada

bunun objektif-c versiyonu var mı?
Syed Ali Salman

Sorun @VasilyBodnarchuk, diğerleri için burada ve pay yapacak
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];
    }
}

Eğer çekirdek Verileri kullanarak ve varsa NSFetchedResultsController, o zaman loadMoreaşağıdaki gibi görünebilir:

// 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];
}

Bunu uygulamaya çalışıyorum ama sqlite yerine bir dizi sonuç kullanıyorum, sahip olduğum mevcut NSMutableArray'e daha fazlasını nasıl ekleyeceğimi ve sonra verileri yeniden yükleyeceğimi merak ediyordum, aksi takdirde verilerin üzerine yazılır ... denedim ... bu [addObjectsFromArray adlarını: [responseObject valueForKeyPath: @ "name"]]; ama çalışmıyor ... sorum için bir bağlantı burada stackoverflow.com/questions/23446780/…
Lion789

1
Her yeni veri aldığınızda verileri yeniden almanın ne anlamı var? Frc doğru yapılandırılırsa, tek getirme yeterlidir, gerektiğinde buna göre güncellenecektir. Frc'nin getirme isteğinin bir ana iş parçacığı bağlamına yapılandırıldığını varsayarsak, her seferinde onu getirmek, ana iş parçacığını diske çarptığında engelleyecektir, bu, kullanıcı yeni veri istediğinde kullanıcı deneyimi için tamamen iyi değildir.
MANIAK_dobrii

Bunun ilk yarısı benim için çok yardımcı oldu, teşekkürler. (FetchedResultsVC kullanılmıyor)
weienw

@MANIAK_dobrii doğru. NSFetchedResultsController'ın temel özelliklerinden biri, sayfalama verilerini hesaplaması ve böylece bir UITableView'a bağladığınızda sanal kaydırmayı ücretsiz olarak alabilmenizdir. Böyle bir loadMore işlevinin uygulanması, yalnızca CoreData deponuzu daha fazla veriyle dolduruyorsanız gerekli olmalıdır; bu durumda, NSFetchedResultsController öğeniz doğru şekilde yapılandırılmışsa başka bir performFetch yapmaya gerek yoktur.
Ali Gangji

Diğer cevaplarla aynı konular. reloadData bunun birden çok kez olmasına neden olur.
dyson

11

Stackoverflow'da bulduğum bir çözümü uyguladım ve iyi çalışıyor, ancak shinyuX'un çözümünün uygulanması çok kolay olduğunu ve önerim için iyi çalıştığını düşünüyorum. Birisi farklı bir çözüm isterse bunu aşağıdaki çözümü kullanabilir.

- (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];
    }
}

Görünüş sunumu için farklı senaryolar olduğunu düşünüyorum, benim durumumda sizin çözümünüz işe yaradı, bunun gibi bir şeye ihtiyacım vardı
Raheel Sadiq

Kullanıcı sert bir şekilde, yani 1.5 ekran yüksekliğinde uçarsa, tetiklenmeden yenilenmeden dibe ulaşılabilir.
dyson

ama listeyi
yukarı kaydır

8

Detaylar

  • Swift 5.1, Xcode 11.3.1

Çözüm

Loadmore için Genetik UITableView Uzantısı.

bu UITableView + Uzantısını yeni dosyanıza ekleyin

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
    }
}

Şimdi, UITableViewDelegate Yöntemine aşağıdaki kod satırını ekleyin, ViewController'ınızda willDisplay Cell ve tableView.delegate = self olduğundan emin olun

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
    }
}

Sonuç

görüntü açıklamasını buraya girin

İşte bu .. Umarım faydalı olur. Teşekkür ederim


Düşünülmesi gereken şeyler. Stoploading fonksiyonunun içine 'tableFooterView = nil' eklemeniz yeterlidir, aksi takdirde gösterge dönüşü animasyonu durdurmaz. Ayrıca activityIndicator'da 'hidesWhenStopped' özelliği vardır, bu nedenle göstergeyi manuel olarak gizli doğru / yanlış ayarlamanıza gerek yoktur. Ama genel harika :) görünüyor
zramled

1
Öneriniz için teşekkürler, bir kez kontrol edip bu yanıtı düzenleyeceğim :-)
Yogesh Patel,

6

Sorgularınızda limit ve offset kullanın ve tablo görünümünüzü bu içerikle doldurun. Kullanıcı aşağı kaydırdığında, bir sonraki ofseti yükleyin.

tableView:willDisplayCell:forRowAtIndexPath:Yöntemi uygulamanıza uygulayın ve UITableViewDelegatebunun son satır olup olmadığını kontrol edin


5

Aşağıdaki bağlantı, örnek kod sağlayacaktır. # Swift3

Sunucudan daha fazla veri almak için kullanıcının son tablo görüntüleme hücresini, en az 2 hücre yüksekliğini çekmesi gerekir.

Son hücrede olduğu gibi yükleme işlemini de gösteren İşlem hücresini bulacaksınız.

Swift3'te

https://github.com/yogendrabagoriya/YBTableViewPullData


3

Kullanmak için bir seçenek daha ( Swift 3 ve 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
             }
         }
     }
 }

Oldukça küçük sayfalar için (~ 10 öğe), 1. ve 2. sayfalar için manuel olarak veri eklemek isteyebilirsiniz, çünkü tabloda iyi kaydırılacak birkaç öğe olana kadar nextPage 1-2 civarında bir yerde olabilir. Ancak sonraki sayfaların tümü için harika çalışacaktır.


1
Bu, yalnızca salt okunur veriler için çalışır. Çalışmıyor Eğer bazı satırları silmek ve pageSize burada düzeltildiğinden daha fazlasını yüklemek gibi işlevlere sahipseniz ve kaynağınızı güncelledikten sonra daha fazla veri olsa bile daha fazla yükleyemiyorsanız.
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;
}

Bu yazıya çok güzel bir açıklama.

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

basit, son satırı eklemeniz ve gizlemeniz gerekir ve tablo satırı son satıra ulaştığında satırı gösterip daha fazla öğe yüklemeniz gerekir.


1

ios UITableViewDataSourcePrefetching'i kontrol etmelisiniz.

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

API'den yüklemek için, Benim için çalışıyor, Xcode 10 , swift 4.2 :

1- Yeni Swift dosyası oluşturun ve şunu yapın:

//
//  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- ViewController dosyanızda (tableView Controller):

//
//  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()

                }})
        }
    }
}

Kişisel viewController seti içinde tableView kullanılıyorsa delege , veri kaynağı viewDidLoad kendini.


0

Sadece bu yaklaşımı paylaşmak istiyorum:

- (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(...); çıktı şöyle bir şey olacaktır:

<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

Bu, yerel olarak depolanan verileri görüntülemek için iyidir. Başlangıçta dataLimit'i 25 olarak beyan ederim, bu uitableview'in 0-24 (başlangıçta) olacağı anlamına gelir.

Kullanıcı aşağıya kaydırırsa ve son hücre görünürse dataLimit25 ...

Not: Bu daha çok bir UITableView veri sayfalama gibidir :)


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];
            });
        }
    }
}
}

son satırı görüntüledikten sonra, satırları ekleyin, yani beginUpdates .. ve çökme olmaması için biraz gecikme kullanın.
Sahila Mirajkar

0

Bu sorunu çözmenin en iyi yolu tablonuzun altına hücre eklemektir ve bu hücre göstergeyi tutacaktır.

Hızlı olarak şunu eklemeniz gerekir:

  1. Hücre türünde yeni bir hücre oluşturYükleme bu göstergeyi tutacaktır. Aşağıdaki koda bakın
  2. Satır sayısına bakın ve buna 1 ekleyin (Bu, hücreyi yüklemek içindir).
  3. idexPath.row == yourArray.count ise rawAtIndex'i kontrol etmeniz ve ardından Yükleme hücresine dönmeniz gerekir.

aşağıdaki koda bakın:

import UIKit

class LoadingCell: UITableViewCell {

@IBOutlet weak var indicator: UIActivityIndicatorView!


}

Tablo görünümü için: 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

}

Yükleme hücremin bir uç dosyasından oluşturulduğunu fark ederseniz. Bu videolar ne yaptığımı açıklayacak.


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

Xcode 10.1, Swift 4.2 için

Bu video harika bir öğretici gibi görünüyor!

Başlangıç ​​/ Tamamlanmış proje: 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()
        })
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.