Tworzę aplikację korzystającą z SQLite. Chcę wyświetlić listę użytkowników (UITableView) przy użyciu mechanizmu paginacji. Czy ktoś mógłby mi powiedzieć, jak załadować więcej danych do mojej listy, gdy użytkownik przewija do końca listy (jak na stronie głównej w aplikacji Facebook)?
ios
iphone
uitableview
pagination
rokridi
źródło
źródło
launchReload
metoda (na przykład tylko jedno asynchroniczne przeładowanie na raz)if !lastItemReached && indexPath.row == dataArray!.hits.count - 1 {
self.launchReload
metoda?Szybki
Metoda 1: Czy przewinięto do dołu
Oto odpowiedź Pedro Romão w wersji Swift . Gdy użytkownik przestaje przewijać, sprawdza, czy doszło do końca.
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() } }
Metoda 2: osiągnięto ostatni wiersz
A oto odpowiedź shinyuX w wersji Swift . Sprawdza, czy użytkownik dotarł do ostatniego wiersza.
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() } }
Przykład
loadMore()
metodySkonfigurowałem te trzy zmienne klasowe do pobierania partii danych.
// 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
Jest to funkcja umożliwiająca załadowanie większej liczby elementów z bazy danych do widoku tabeli.
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 } } } }
źródło
Lepiej użyć
willDisplayCell
metody, aby sprawdzić, która komórka zostanie załadowana. Gdy uzyskamy prądindexPath.row
, możemy załadować więcej ogniw. Spowoduje to załadowanie większej liczby komórek podczas przewijania w dół.- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { // check if indexPath.row is last row // Perform operation to load new Cell's. }
źródło
Detale
Rozwiązanie
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() } }
Stosowanie
w tym
activityIndicator = LoadMoreActivityIndicator(scrollView: tableView, spacingFromLastCell: 10, spacingFromLastCellWhenLoadMoreActionStart: 60)
obsługa
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() } } } } }
Pełna próbka
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() } } } } }
Wynik
źródło
- (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]; } }
Jeśli korzystasz z danych podstawowych i
NSFetchedResultsController
,loadMore
może to wyglądać następująco:// 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]; }
źródło
Wdrożyłem jedno rozwiązanie, które znalazłem w stackoverflow i działa dobrze, ale myślę, że rozwiązanie shinyuX jest bardzo łatwe do wdrożenia i działa dobrze dla mojej propozycji. Jeśli ktoś chce innego rozwiązania, może skorzystać z tego poniżej.
- (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]; } }
źródło
Detale
Rozwiązanie
dodaj to rozszerzenie UITableView + do nowego pliku
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 } }
Teraz po prostu dodaj następujący wiersz kodu w metodzie UITableViewDelegate willDisplay Cell w Twoim ViewController i upewnij się, że 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 } }
Wynik
To wszystko… Mam nadzieję, że to pomocne. Dziękuję Ci
źródło
Użyj limitu i przesunięcia w swoich zapytaniach i wypełnij widok tabeli tą zawartością. Gdy użytkownik przewinie w dół, załaduj następny offset.
Zaimplementuj
tableView:willDisplayCell:forRowAtIndexPath:
metodę w swoimUITableViewDelegate
i sprawdź, czy to ostatni wierszźródło
Poniższy link zawiera przykładowy kod. # Swift3
Użytkownik musi podciągnąć ostatnią komórkę widoku tabeli, co najmniej 2 komórki, aby pobrać więcej danych z serwera.
Znajdziesz komórkę procesową, która również pokazuje proces ładowania jak w ostatniej komórce.
Jest w Swift3
https://github.com/yogendrabagoriya/YBTableViewPullData
źródło
Jeszcze jedna opcja do użycia ( Swift 3 i 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 } } } }
W przypadku raczej małych stron (~ 10 pozycji) możesz chcieć ręcznie dodać dane dla stron 1 i 2, ponieważ nextPage może znajdować się gdzieś około 1-2, dopóki w tabeli nie będzie kilku elementów do dobrego przewinięcia. Ale będzie świetnie działać na wszystkich następnych stronach.
źródło
- (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; }
bardzo dobre wyjaśnienie tego postu.
http://useyourloaf.com/blog/2010/10/02/dynamically-loading-new-rows-into-a-table.html
proste, musisz dodać ostatni wiersz i ukryć go, a gdy wiersz tabeli trafi w ostatni wiersz, pokaż wiersz i załaduj więcej elementów.
źródło
powinieneś sprawdzić 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)") } }
źródło
do ładowania z API, to działa dla mnie, Xcode 10 , swift 4.2 :
1- utwórz nowy plik Swift i zrób tak:
// // 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- w pliku ViewController (kontroler 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() }}) } } }
jeśli używasz tableView w Twoim viewController ustaw delegata , własne źródło danych w viewDidLoad.
źródło
Chcę tylko podzielić się tym podejściem:
- (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(...);
wynik będzie wyglądał następująco:<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
Jest to dobre do wyświetlania danych przechowywanych lokalnie. Początkowo deklaruję dataLimit na 25, co oznacza, że uitableview będzie miało 0-24 (początkowo).
Jeśli użytkownik przewinął w dół i ostatnia komórka jest widoczna,
dataLimit
zostanie dodana 25 ...Uwaga: to bardziej przypomina stronicowanie danych UITableView :)
źródło
-(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]; }); } } } }
źródło
Najlepszym sposobem rozwiązania tego problemu jest dodanie komórki na dole tabeli, a ta komórka będzie zawierać wskaźnik.
Szybko musisz dodać to:
spójrz na kod poniżej:
import UIKit class LoadingCell: UITableViewCell { @IBOutlet weak var indicator: UIActivityIndicatorView! }
W przypadku widoku tabeli: 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 }
Jeśli zauważysz, że moja komórka ładująca jest tworzona z pliku nib. Te filmy wyjaśniają, co zrobiłem.
źródło
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 } } }
źródło
W przypadku Xcode 10.1, Swift 4.2
Ten film wydaje się świetnym samouczkiem!
Projekt początkowy / kompletny: 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() }) } }
źródło