UITableView 在像 Facebook 应用程序一样滚动到底部时加载更多
Posted
技术标签:
【中文标题】UITableView 在像 Facebook 应用程序一样滚动到底部时加载更多【英文标题】:UITableView load more when scrolling to bottom like Facebook application 【发布时间】:2013-12-14 16:32:05 【问题描述】:我正在开发一个使用 SQLite 的应用程序。我想使用分页机制显示用户列表(UITableView)。谁能告诉我当用户滚动到列表末尾时如何在我的列表中加载更多数据(比如在 Facebook 应用程序的主页上)?
【问题讨论】:
【参考方案1】:您可以通过在cellForRowAtIndexPath:
方法中添加对您所在位置的检查来做到这一点。此方法易于理解和实现:
- (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];
编辑:添加了对最后一项的检查以防止递归调用。您必须实现定义是否已到达最后一项的方法。
EDIT2:解释 lastItemReached
【讨论】:
如果用户上下滚动怎么办,所以 cellForRowAtIndexPath 被多次调用!?? 第一次滚动到底部时,他的列表将被重新加载。每次他触底时,都会收集新的数据块。如果必须应用任何特定处理,则由launchReload
方法负责处理(例如,一次只能执行一个异步重新加载操作)
我必须添加一个标志以防止在最后一项被击中时出现递归问题:if !lastItemReached && indexPath.row == dataArray!.hits.count - 1
self.launchReload
方法是什么?
@shinyuX 对我不起作用,“如果”总是错误的...但是如果 (lastItemReached && indexPath.row == [self.dataArray count] - 1) 为真,为什么?【参考方案2】:
斯威夫特
方法一:滚动到底部
这是Pedro Romão's answer 的 Swift 版本。当用户停止滚动时,它会检查它是否已到达底部。
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()
方法2:到达最后一行
这里是shinyuX's answer 的 Swift 版本。它检查用户是否已到达最后一行。
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()
loadMore()
方法示例
我设置了这三个类变量来获取批量数据。
// 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
这是将更多项目从数据库加载到表视图中的功能。
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
【讨论】:
我使用了方法 1,因为我想拉取更多。它工作得很好。谢谢你们!【参考方案3】:最好使用willDisplayCell
方法来检查是否将加载哪个单元格。
一旦我们得到当前的indexPath.row
是最后一个,我们就可以加载更多的单元格。
这将在向下滚动时加载更多单元格。
- (void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
// check if indexPath.row is last row
// Perform operation to load new Cell's.
【讨论】:
不是更好,因为 reloadData 会再次调用此方法对吗? 是的,这适用于部分,indexPath 将为您提供行和部分。【参考方案4】:详情
Swift 5.1,Xcode 11.2.1解决方案
使用 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)
if #available(ios 13.0, *)
activityIndicatorView.color = .label
else
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()
用法
初始化
activityIndicator = LoadMoreActivityIndicator(scrollView: tableView, spacingFromLastCell: 10, spacingFromLastCellWhenLoadMoreActionStart: 60)
处理
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()
完整样本
不要忘记粘贴解决方案代码。
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()
结果
【讨论】:
完美运行。但是我的表格视图中有一个标题,在拖动以加载更多内容后,标题将位于导航栏下方.. loadMoreActionFinshed 中的 UIEdgeInsetsMake 应设置为 (62, 0, 0, 0) 考虑到 66 = navbar.height + 22 垂直滚动时它应该在 CollectionView 中工作。 难以置信...酷! 这个的任何objective-c版本? @VasilyBodnarchuk 没问题,我会做的,在这里分享给其他人【参考方案5】:- (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];
如果您使用 Core Data 和 NSFetchedResultsController
,那么 loadMore
可能如下所示:
// 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];
【讨论】:
我正在尝试实现这一点,但我使用的是结果数组而不是 sqlite,想知道如何在我拥有的当前 NSMutableArray 中添加更多内容,然后重新加载数据,否则数据被覆盖...我试过这个 [names addObjectsFromArray: [responseObject valueForKeyPath:@"name"]];但它不起作用......这是我的问题的链接***.com/questions/23446780/… 每次获取新数据时重新获取数据有什么意义?如果 frc 配置正确,单次 fetch 就足够了,它会根据需要进行相应的更新。每次都获取它,假设frc的获取请求配置到主线程上下文将阻塞主线程,因为它撞击磁盘,当用户想要新数据时,这完全不利于用户体验。 前半部分对我很有帮助,谢谢。 (不使用 FetchedResultsVC) @MANIAK_dobrii 是正确的。 NSFetchedResultsController 的关键特性之一是它计算分页数据,以便在将其连接到 UITableView 时免费获得虚拟滚动。仅当您实际上用更多数据填充 CoreData 存储时才需要实现这样的 loadMore 函数,在这种情况下,如果您的 NSFetchedResultsController 配置正确,则无需执行另一个 performFetch。 与其他答案相同的问题。 reloadData 导致这种情况发生多次。【参考方案6】:详情
Swift 5.1,Xcode 11.3.1解决方案
Loadmore 的遗传 UITableView 扩展。
在你的新文件中添加这个 UITableView + Extension
extension UITableView
func indicatorView() -> UIActivityIndicatorView
var activityIndicatorView = UIActivityIndicatorView()
if self.tableFooterView == nil
let indicatorFrame = CGRect(x: 0, y: 0, width: self.bounds.width, height: 80)
activityIndicatorView = UIActivityIndicatorView(frame: indicatorFrame)
activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
if #available(iOS 13.0, *)
activityIndicatorView.style = .large
else
// Fallback on earlier versions
activityIndicatorView.style = .whiteLarge
activityIndicatorView.color = .systemPink
activityIndicatorView.hidesWhenStopped = 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()
func stopLoading()
if self.tableFooterView != nil
self.indicatorView().stopAnimating()
self.tableFooterView = nil
else
self.tableFooterView = nil
现在,只需在 UITableViewDelegate Method willDisplay Cell 中添加以下代码行并确保 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
结果
就是这样..希望这有帮助。谢谢你
【讨论】:
需要考虑的事项。只需在 stoploading 函数中添加 'tableFooterView = nil' ,否则指示器旋转不会停止动画。 activityIndicator 'hidesWhenStopped' 中还有一个属性,因此您无需手动设置隐藏的真/假指标。但总的来说它看起来很棒:) 感谢您的建议,我将检查一次并编辑此答案:-) 谢谢兄弟。这个答案比互联网上任何其他解决方案都要好。 只是几件事。 1)每次要显示单元格时都会调用“addLoading”。在检查 lastVisibleIndexPath 等之前,indicatorView 不应启动动画。 2)“addLoading”使用indicatorView,每次用UIActivityIndicatorView()创建。如果 tableViewFooter 为 nil,则您已经从那里创建了它。如果 tableViewFooter 不是 nil,你可以检查它是否真的是 UIActivityIndicatorView,而不是创建一个新的。【参考方案7】:我已经实现了我在 *** 中找到的一种解决方案,它运行良好,但我认为 shinyuX 的解决方案非常容易实现,并且对于我的建议运行良好。 如果有人想要不同的解决方案,可以使用下面的这个。
- (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];
【讨论】:
我认为视图呈现有不同的场景,在我的情况下你的解决方案有效,我需要这样的东西 如果用户用力一甩,即1.5屏幕高,可以到达底部而不触发刷新。 但它会将列表滚动到顶部【参考方案8】:在您的查询中使用限制和偏移量,并用该内容填充您的表格视图。当用户向下滚动时,加载下一个偏移量。
在您的UITableViewDelegate
中实现tableView:willDisplayCell:forRowAtIndexPath:
方法并检查它是否是最后一行
【讨论】:
【参考方案9】:以下链接将提供示例代码。 #Swift3
用户需要拉起最后一个表格视图单元格,至少 2 个单元格的高度才能从服务器获取更多数据。
您会发现 Process 单元格也显示加载过程,就像在最后一个单元格中一样。
它在 Swift3 中
https://github.com/yogendrabagoriya/YBTableViewPullData
【讨论】:
【参考方案10】:另一个使用选项(Swift 3 和 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
对于相当小的页面(约 10 个项目),您可能需要手动为第 1 页和第 2 页添加数据,因为 nextPage 可能在大约 1-2 的某个位置,直到表格有几个项目可以很好地滚动。但它适用于所有后续页面。
【讨论】:
这仅适用于只读数据。不起作用如果您具有删除某些行并加载更多的功能,因为 pageSize 已在此处修复,并且即使在更新源后有更多数据也无法加载更多。【参考方案11】:- (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;
这篇文章的解释很好。
http://useyourloaf.com/blog/2010/10/02/dynamically-loading-new-rows-into-a-table.html
很简单,您必须添加最后一行并将其隐藏,当表格行到达最后一行时,而不是显示该行并加载更多项目。
【讨论】:
【参考方案12】:你应该检查 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)")
https://developer.apple.com/documentation/uikit/uitableviewdatasourceprefetching
https://andreygordeev.com/2017/02/20/uitableview-prefetching/
【讨论】:
【参考方案13】:对于 Xcode 10.1、Swift 4.2
This video 似乎是一个很棒的教程!
启动/完成项目: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()
)
【讨论】:
【参考方案14】:用于从 API 加载,它适用于我,Xcode10,swift 4.2:
1- 创建新的 Swift 文件并这样做:
//
// 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 文件(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()
)
如果在你的 viewController 中使用 tableView 设置 delegate,datasource self in viewDidLoad.
【讨论】:
【参考方案15】:只想分享这种方法:
- (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(...);
输出类似于:
<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
这对于显示本地存储的数据很有用。 最初我将 dataLimit 声明为 25,这意味着 uitableview 将有 0-24(最初)。
如果用户滚动到底部并且最后一个单元格可见,dataLimit
将添加 25...
注意:这更像是一个 UITableView 数据分页,:)
【讨论】:
【参考方案16】:-(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];
);
【讨论】:
在显示最后一行之后,插入行,即 beginUpdates..并使用一些延迟来避免崩溃。【参考方案17】:解决此问题的最佳方法是在表格底部添加单元格,该单元格将包含指示器。
在swift中你需要添加这个:
-
创建 cellLoading 类型的新单元格,这将保存指标。看下面的代码
查看行数并加1(这是用于加载单元格)。
如果 idexPath.row == yourArray.count 则需要检查 rawAtIndex,然后返回加载单元格。
看看下面的代码:
import UIKit
class LoadingCell: UITableViewCell
@IBOutlet weak var indicator: UIActivityIndicatorView!
对于表格视图: numOfRows:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return yourArray.count + 1
cellForRawAt 索引路径:
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
如果您注意到我的加载单元是从 nib 文件创建的。 This videos 会解释我做了什么。
【讨论】:
【参考方案18】: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
【讨论】:
【参考方案19】:这是示例代码。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
let cell:ShowComplainCell = tableView.dequeueReusableCell(withIdentifier: "cell")! as! ShowComplainCell
let item = self.dataArray[indexPath.row] as! ComplainListItem;
let indexPathArray = NSArray(array: tableView.indexPathsForVisibleRows!)
let vIndexPath = indexPathArray.lastObject as! NSIndexPath
let lastItemReached = item.isEqual(self.dataArray.lastObject);
if (lastItemReached && vIndexPath.row == (self.dataArray.count - 1))
self.loadData()
return cell
indexPathArray: 是可见的行。
vIndexPath:最后一个索引路径可见
加载数据
func loadData()
if(isReloadTable)
let HUD = MBProgressHUD.showAdded(to: self.view, animated: true)
let manager :AFHTTPSessionManager = AFHTTPSessionManager()
var param = NSDictionary()
param = [
"category":cat_id,
"smart_user_id": USERDEF.value(forKey: "user_id") as! String,
"page":page,
"phone":phone! as String
] as [String : Any] as NSDictionary
print("param1 = \(param)")
manager.get("lists.php?", parameters: param, progress: nil, success: (task:URLSessionDataTask, responseObject: Any) in
let adsArray = dic["results"] as! NSArray;
for item in adsArray
let item = ComplainListItem(dictionary: item as! NSDictionary )
self.dataArray.add(item)
self.view.addSubview(self.cityTableView)
self.cityTableView.reloadData()
if(adsArray.count==10)
self.cityTableView.reloadData()
self.isReloadTable = true
self.page+=1
else if(adsArray.count<10)
self.cityTableView.reloadData()
self.isReloadTable = false
HUD.hide(animated:true)
) (operation,error) -> Void in
print("error = \(error)")
HUD.hide(animated:true)
检查您的 dataArray 计数,即 myadsarray 检查是否等于您的数据限制。然后如果dataArray count等于下一页,如果不等于小于10,则调用所有数据。
【讨论】:
以上是关于UITableView 在像 Facebook 应用程序一样滚动到底部时加载更多的主要内容,如果未能解决你的问题,请参考以下文章
IOS:如何将 UITableView 用作 Facebook 类应用程序的 Newsfeed 视图
Facebook 在 uitableview 中加载相册,但未在下一个收藏视图中打开
UITableView 滚动时隐藏工具栏元素(类似于 Facebook 的应用?)
Swift:UITableView - 滑动删除 - 如何让单元格内容在滑动时不移动
Facebook Graph API GET 请求 - 应包含“字段”参数(Swift,Facebook SDK v4.5.1)