UITableView memuat lebih banyak saat menggulir ke bawah seperti aplikasi Facebook

96

Saya mengembangkan aplikasi yang menggunakan SQLite. Saya ingin menampilkan daftar pengguna (UITableView) menggunakan mekanisme penomoran halaman. Adakah yang bisa memberi tahu saya cara memuat lebih banyak data di daftar saya saat pengguna menggulir ke akhir daftar (seperti di halaman beranda pada aplikasi Facebook)?

rokridi
sumber

Jawaban:

103

Anda dapat melakukannya dengan menambahkan tanda centang pada posisi Anda dalam cellForRowAtIndexPath:metode ini. Metode ini mudah dipahami dan diterapkan:

- (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: menambahkan centang pada item terakhir untuk mencegah panggilan rekursi. Anda harus menerapkan metode yang menentukan apakah item terakhir telah tercapai atau belum.

EDIT2: menjelaskan lastItemReached

shinyuX
sumber
9
Bagaimana jika pengguna menggulir ke atas dan ke bawah, sehingga cellForRowAtIndexPath disebut BANYAK KALI! ??
onmyway133
Pertama kali dia menggulir ke bawah daftarnya akan dimuat ulang. Dan setiap kali dia mencapai dasar, sejumlah data baru akan dikumpulkan. Jika ada perlakuan khusus yang harus diterapkan, itu akan menjadi launchReloadtanggung jawab metode untuk menanganinya (misalnya, hanya satu tindakan muat ulang asinkron pada satu waktu)
shinyuX
4
Saya harus menambahkan bendera untuk mencegah masalah rekursi ketika item terakhir terkena:if !lastItemReached && indexPath.row == dataArray!.hits.count - 1 {
Albert Bori
Bagaimana self.launchReloadcaranya?
slider
1
@shinyuX tidak berfungsi untuk saya, "jika" selalu salah ... tetapi jika (lastItemReached && indexPath.row == [self.dataArray count] - 1) benar, MENGAPA?
Mengatakan
69

Cepat

Metode 1: Gulir ke bawah

Ini adalah versi Swift dari jawaban Pedro Romão . Saat pengguna berhenti menggulir, ia memeriksa apakah telah mencapai bagian bawah.

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

Metode 2: Mencapai baris terakhir

Dan inilah jawaban shinyuX versi Swift . Ia memeriksa apakah pengguna telah mencapai baris terakhir.

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

}

Contoh loadMore()metode

Saya menyiapkan tiga variabel kelas ini untuk mengambil kumpulan data.

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

Ini adalah fungsi untuk memuat lebih banyak item dari database ke dalam tampilan tabel.

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
            }

        }
    }
}
Suragch
sumber
Saya menggunakan Metode 1, karena saya ingin menarik-untuk-mengambil-lebih. Ini bekerja dengan baik. Terima kasih untuk kalian berdua!
Bob Wakefield
37

Lebih baik menggunakan willDisplayCellmetode untuk memeriksa apakah sel mana yang akan dimuat. Setelah kita mendapatkan arus indexPath.rowterakhir kita dapat memuat lebih banyak sel. Ini akan memuat lebih banyak sel saat menggulir ke bawah.

 - (void)tableView:(UITableView *)tableView 
       willDisplayCell:(UITableViewCell *)cell    
       forRowAtIndexPath:(NSIndexPath *)indexPath
{
    // check if indexPath.row is last row
    // Perform operation to load new Cell's.
}
Suraj Mirajkar
sumber
16
itu tidak lebih baik karena reloadData akan memanggil metode ini lagi kan?
Marcin
Akankah ini berfungsi jika kita memiliki bagian juga?
Abdul Yasin
Ya, ini akan berfungsi untuk bagian, indexPath akan memberi Anda baris & bagian keduanya.
Suraj Mirajkar
24

Detail

  • Swift 5.1, Xcode 11.2.1

Larutan

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

Pemakaian

init

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

penanganan

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

Sampel Lengkap

Jangan lupa paste kode solusinya.

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

Hasil

masukkan deskripsi gambar di sini

Bodnarchuk dengan mudah
sumber
Bekerja dengan sempurna. Tapi saya memiliki header di tableview saya, setelah drag untuk memuat lebih banyak, header akan berada di bawah bilah navigasi .. UIEdgeInsetsMake di loadMoreActionFinshed harus disetel ke (62, 0, 0, 0) mengingat 66 = navbar.height + 22
Desmond
Ini harus berfungsi di CollectionView saat Anda menggulir secara vertikal.
Vasily Bodnarchuk
Luar biasa ... Keren!
Tà Truhoada
ada versi objektif-c ini?
Syed Ali Salman
@VasilyBodnarchuk tidak masalah, saya akan melakukannya dan berbagi di sini untuk orang lain
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];
    }
}

Jika Anda menggunakan Core Data dan NSFetchedResultsController, maka loadMoreakan terlihat seperti berikut:

// 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];
}
samwize
sumber
Saya mencoba menerapkan ini tetapi saya menggunakan serangkaian hasil dan bukan sqlite, bertanya-tanya bagaimana saya akan menambahkan lebih banyak ke dalam NSMutableArray saat ini yang saya miliki dan kemudian memuat ulang data, karena jika tidak data akan ditimpa ... Saya mencoba ini [nama addObjectsFromArray: [responseObject valueForKeyPath: @ "name"]]; tetapi tidak berfungsi ... ini tautan ke pertanyaan saya stackoverflow.com/questions/23446780/…
Lion789
1
Apa gunanya mengambil kembali data setiap kali Anda mendapatkan data baru? Jika frc dikonfigurasi dengan benar, pengambilan tunggal sudah cukup, itu akan diperbarui sesuai kebutuhan. Mengambilnya setiap saat, dengan asumsi permintaan pengambilan frc dikonfigurasi ke konteks utas utama akan memblokir utas utama saat menyentuh disk, itu sama sekali tidak baik untuk pengalaman pengguna ketika pengguna menginginkan data baru.
MANIAK_dobrii
Paruh pertama ini sangat membantu saya, terima kasih. (Tidak menggunakan FetchedResultsVC)
weienw
@MANIAKdobrii benar. Salah satu fitur utama NSFetchedResultsController adalah ia menghitung data paging sehingga Anda mendapatkan pengguliran virtual secara gratis saat Anda menghubungkannya ke UITableView. Menerapkan fungsi loadMore seperti itu seharusnya hanya diperlukan jika Anda benar-benar mengisi penyimpanan CoreData Anda dengan lebih banyak data, dalam hal ini tidak perlu melakukan performFetch lagi jika NSFetchedResultsController Anda dikonfigurasi dengan benar.
Ali Gangji
Masalah yang sama seperti jawaban lainnya. reloadData menyebabkan ini terjadi beberapa kali.
Dyson kembali
11

Saya telah menerapkan satu solusi yang saya temukan di stackoverflow, dan berfungsi dengan baik, tetapi menurut saya solusi shinyuX sangat mudah diterapkan dan berfungsi dengan baik untuk usulan saya. Jika seseorang menginginkan solusi yang berbeda dapat menggunakan yang ini di bawah ini.

- (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];
    }
}
Pedro Romão
sumber
Saya pikir ada skenario berbeda untuk tampilan presentasi, dalam kasus saya solusi Anda berhasil, saya membutuhkan sesuatu seperti ini
Raheel Sadiq
Jika pengguna melempar dengan keras, misalnya dengan tinggi 1,5 layar, bagian bawah dapat dijangkau tanpa pemuatan ulang.
Dyson kembali
tetapi menggulir daftar ke atas
Mansuu ....
7

Detail

  • Swift 5.1, Xcode 11.3.1

Larutan

Ekstensi Genetic UITableView Untuk Loadmore.

tambahkan Ekstensi UITableView + ini di file baru Anda

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

Sekarang, cukup tambahkan baris kode berikut dalam Metode UITableViewDelegate willDisplay Cell di ViewController Anda dan pastikan 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
    }
}

Hasil

masukkan deskripsi gambar di sini

Itu saja .. Semoga ini membantu. Terima kasih

Yogesh Patel
sumber
Hal-hal yang perlu diperhatikan. Cukup tambahkan 'tableFooterView = nil' di dalam fungsi stoploading jika tidak, putaran indikator tidak akan berhenti beranimasi. Juga ada properti di activityIndicator 'hidesWhenStopped' sehingga Anda tidak perlu menyetel indikator tersembunyi benar / salah secara manual. Tapi secara keseluruhan itu terlihat bagus :)
zramled
1
Terima kasih atas sarannya, saya akan memeriksa sekali dan mengedit jawaban ini :-)
Yogesh Patel
6

Gunakan batas dan offset dalam kueri Anda dan isi tampilan tabel Anda dengan konten itu. Saat pengguna menggulir ke bawah, muat offset berikutnya.

Terapkan tableView:willDisplayCell:forRowAtIndexPath:metode di Anda UITableViewDelegatedan periksa untuk melihat apakah itu baris terakhir

Retterdesdialog
sumber
5

Tautan di bawah ini akan memberikan kode contoh. # Swift3

Pengguna perlu menarik sel tampilan tabel terakhir, setidaknya tinggi 2 sel untuk mengambil lebih banyak data dari server.

Anda akan menemukan sel Proses juga untuk menunjukkan proses pemuatan seperti pada sel terakhir.

Ada di Swift3

https://github.com/yogendrabagoriya/YBTableViewPullData

Yogi
sumber
3

Satu lagi opsi untuk digunakan ( Swift 3 dan 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
             }
         }
     }
 }

Untuk halaman yang agak kecil (~ 10 item) Anda mungkin ingin menambahkan data secara manual untuk halaman 1 dan 2 karena nextPage mungkin berada di sekitar 1-2 hingga tabel memiliki beberapa item untuk di-scroll dengan baik. Tapi itu akan bekerja dengan baik untuk semua halaman berikutnya.

Vitalii
sumber
1
Ini berfungsi hanya untuk data hanya baca. Tidak berfungsi Jika Anda memiliki fungsionalitas seperti menghapus beberapa baris dan memuat lebih banyak karena pageSize telah diperbaiki di sini dan tidak dapat memuat lebih banyak bahkan Jika ada lebih banyak data setelah Anda memperbarui sumber Anda.
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;
}

penjelasan yang sangat bagus di posting ini.

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

sederhana Anda harus menambahkan baris terakhir dan menyembunyikannya dan ketika baris tabel mencapai baris terakhir daripada menampilkan baris dan memuat lebih banyak item.

vlad sol
sumber
1

Anda harus memeriksa 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)")
    }


}
Bhavesh.iosDev
sumber
1

untuk memuat dari API, Ini berfungsi untuk saya, Xcode 10 , swift 4.2 :

1- buat file Swift Baru dan lakukan seperti ini:

//
//  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- Dalam file ViewController Anda (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()

                }})
        }
    }
}

jika menggunakan tableView di Delegasi set viewController Anda , sumber data sendiri dalam viewDidLoad.

hooma7n
sumber
0

Hanya ingin berbagi pendekatan ini:

- (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(...); keluarannya akan seperti ini:

<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

Ini bagus untuk menampilkan data yang disimpan secara lokal. Awalnya saya mendeklarasikan dataLimit ke 25, itu berarti uitableview akan memiliki 0-24 (awalnya).

Jika pengguna menggulir ke bawah dan sel terakhir terlihat dataLimit akan ditambahkan dengan 25 ...

Catatan: Ini lebih seperti paging data UITableView, :)

0yeoj
sumber
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];
            });
        }
    }
}
}
Sahila Mirajkar
sumber
setelah menampilkan baris terakhir, masukkan baris yaitu beginUpdates..dan gunakan beberapa penundaan agar tidak ada crash.
Sahila Mirajkar
0

Cara terbaik untuk mengatasi masalah ini adalah dengan menambahkan sel di bagian bawah tabel Anda, dan sel ini akan menampung indikator.

Anda perlu segera menambahkan ini:

  1. Buat sel baru dengan tipe cellLoading ini akan menahan indikator. Lihat kode di bawah ini
  2. Lihatlah jumlah baris dan tambahkan 1 ke dalamnya (Ini untuk memuat sel).
  3. Anda perlu memeriksa di rawAtIndex jika idexPath.row == yourArray.count lalu mengembalikan sel Memuat.

lihat kode di bawah ini:

import UIKit

class LoadingCell: UITableViewCell {

@IBOutlet weak var indicator: UIActivityIndicatorView!


}

Untuk tampilan tabel: 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

}

Jika Anda memperhatikan bahwa sel pemuatan saya dibuat dari file pena. Video ini akan menjelaskan apa yang saya lakukan.

Mohammed Ali Khaled
sumber
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
        }
    }
  }
Alok SInha
sumber
0

Untuk Xcode 10.1, Swift 4.2

Video ini sepertinya tutorial yang bagus!

Proyek Pemula / Selesai: 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()
        })
    }
}
Shuvo Joseph
sumber