Swift tableView 分页
Posted
技术标签:
【中文标题】Swift tableView 分页【英文标题】:Swift tableView Pagination 【发布时间】:2016-05-23 21:23:57 【问题描述】:我已经成功地使用 json 解析代码工作了 tableview。但是可能还有 1000 多个项目,所以在滚动底部时需要分页。我不知道如何在下面的代码中执行此操作。对于objective-c 有很多示例,但对于swift 我没有找到工作示例。我在等你的帮助。我想会帮助太多人。谢谢 !
import UIKit
class ViewController: UIViewController, UITableViewDataSource,UITableViewDelegate
let kSuccessTitle = "Congratulations"
let kErrorTitle = "Connection error"
let kNoticeTitle = "Notice"
let kWarningTitle = "Warning"
let kInfoTitle = "Info"
let kSubtitle = "You've just displayed this awesome Pop Up View"
@IBOutlet weak var myTableView: UITableView!
@IBOutlet weak var myActivityIndicator: UIActivityIndicatorView!
var privateList = [String]()
override func viewDidLoad()
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
override func viewWillAppear(animated: Bool)
super.viewWillAppear(animated)
loadItems()
override func didReceiveMemoryWarning()
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return privateList.count
internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
let cell:myCell = tableView.dequeueReusableCellWithIdentifier("myCell") as! myCell
cell.titleLabel.text = privateList[indexPath.row]
return cell
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)
if (editingStyle == UITableViewCellEditingStyle.Delete)
print(indexPath.row)
let alert = SCLAlertView()
alert.addButton("Hayır")
alert.addButton("Evet")
self.myTableView.beginUpdates()
self.privateList.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Left)
print("Silindi")
self.myTableView.endUpdates()
self.loadItems()
alert.showSuccess(kSuccessTitle, subTitle: kSubtitle)
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool
// the cells you would like the actions to appear needs to be editable
return true
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
if(segue.identifier == "Detail")
let destinationView = segue.destinationViewController as! DetailViewController
if let indexPath = myTableView.indexPathForCell(sender as! UITableViewCell)
destinationView.privateLista = privateList[indexPath.row]
internal func tableView(tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat
return 0.0
func loadItems()
loadItemsNow("privateList")
func loadItemsNow(listType:String)
myActivityIndicator.startAnimating()
let listUrlString = "http://bla.com/json2.php?listType=" + listType + "&t=" + NSUUID().UUIDString
let myUrl = NSURL(string: listUrlString);
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "GET";
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
data, response, error in
if error != nil
print(error!.localizedDescription)
dispatch_async(dispatch_get_main_queue(),
self.myActivityIndicator.stopAnimating()
)
return
do
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSArray
if let parseJSON = json
self.privateList = parseJSON as! [String]
catch
print(error)
dispatch_async(dispatch_get_main_queue(),
self.myActivityIndicator.stopAnimating()
self.myTableView.reloadData()
)
task.resume()
【问题讨论】:
您是否一次获取数千个对象? @ShehzadAli 不,但在滚动底部时需要刷新新的会很好。还有很多项目会花费很多时间。 【参考方案1】:另一种方法是:您可以在每次发送请求时设置获取元素的阈值:
假设您是第一次获取 20 个元素。您将保存最后获取的记录 ID 或编号,以获取接下来 20 个元素的列表。
let lastFetchedIndex = 20;
我假设您已经在 myArray.xml 中添加了这些记录。 MyArray 是 tableView 的数据源。现在 myArray 包含 40 个对象。我要列出现在需要在 tableView 中插入的行的 indexPaths 列表。
var indexPathsArray = [NSIndexPath]()
for index in lastFetchedIndex..<myArray.count
let indexPath = NSIndexPath(forRow: index, inSection: 0)
indexPathsArray.append(indexPath)
在这里我正在更新我的 tableView。确保您的数据源我的意思是您的 myArray 已经更新。以便它可以正确插入行。
self.tableView.beginUpdates()
tableView!.insertRowsAtIndexPaths(indexPathsArray, withRowAnimation: .Fade)
self.tableView.endUpdates()
【讨论】:
感谢老兄的回答,但我在哪里可以添加代码? 我已经分步说明了。在获得大量新对象后,使用此代码更新您的代码。将来自服务器的项目块添加到用作 tableview 数据源的数组中。然后在您的代码中添加上述代码。上面的代码可以放在一个方法中。 请编辑您的答案并过去我的原始代码并使用您的分页代码进行编辑,稍后我会检查,如果有效,我会批准您的答案..【参考方案2】:为此,您还需要更改服务器端。
服务器将接受API
url 中的fromIndex
和batchSize
作为查询参数。
let listUrlString = "http://bla.com/json2.php?listType=" + listType + "&t=" + NSUUID().UUIDString + "&batchSize=" + batchSize + "&fromIndex=" + fromIndex
在服务器响应中,会有一个额外的密钥totalItems
。这将用于识别是否收到所有物品。数组或项目 fromIndex
到 batchSize
的项目数。
在应用端
首先将使用fromIndex = 0
和batchSize = 20
调用loadItem()
(例如在viewDidLoad()
或viewWillAppear
中)。在第一次调用 loadItem()
之前从 privateList
数组中删除所有项目
服务器返回一个包含前 20 个项目的数组和totalItems
服务器中的项目总数。
追加privateList
数组中的20个项目并重新加载tableView
在tableView:cellForRowAtIndexPath
方法中检查单元格是否是最后一个单元格。并检查totalItems
(表单服务器)是否大于privateList.count
。这意味着服务器中有更多的项目要加载
if indexPath.row == privateList.count - 1 // last cell
if totalItems > privateList.count // more items to fetch
loadItem() // increment `fromIndex` by 20 before server call
问题: where is refresh ? will be scrolling ?
收到服务器响应后,在数组中添加新项目后刷新。 (第 3 步)
当用户滚动时,每个单元格都会触发tableView:cellForRowAtIndexPath
。代码正在检查它是否是最后一个单元格并获取剩余的项目。 (第 4 步)
已添加示例项目:https://github.com/rishi420/TableViewPaging
【讨论】:
刷新在哪里?会滚动吗? 你能写完整代码集成完整代码我将测试 你能写完整代码集成完整代码我将测试 @SwiftDeveloper 答案更新为示例项目链接。下载、运行并查看print
日志。
:) 给我一个示例 url http://bla.com/json2.php?listType=...
我可以点击和测试【参考方案3】:
我在一个项目中需要类似的东西,我的解决方案是:
1 - 创建一个变量 numberOfObjectsInSubArray(初始值 30 或任何你想要的)
2 - 每次点击“显示更多”时,创建一个子数组以从您的 privateList 数组中添加一些对象
let subArray = privateList?.subarrayWithRange(NSMakeRange(0, numberOfObjectsInSubArray))
然后用它来
internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return subArray.count
3- 每当您需要显示更多对象时,请执行以下操作:
func addMoreObjectsOnTableView ()
numberOfObjectsInSubArray += 30
if (numberOfObjectsInSubArray < privateList.count)
subArray = privateList?.subarrayWithRange(NSMakeRange(0, numberOfObjectsInSubArray))
else
subArray = privateList?.subarrayWithRange(NSMakeRange(0, privateList.count))
tableView.reloadData()
希望对你有帮助
【讨论】:
【参考方案4】:在您的表格视图中添加另一个部分,让该部分只有 1 行,这将是一个包含活动指示器的单元格,以表示正在加载。
internal func numberOfSectionsInTableView(tableView: UITableView) -> Int
return 2;
internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
if section == 0
return privateList.count
else if section == 1 // this is going to be the last section with just 1 cell which will show the loading indicator
return 1
internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
if section == 0
let cell:myCell = tableView.dequeueReusableCellWithIdentifier("myCell") as! myCell
cell.titleLabel.text = privateList[indexPath.row]
return cell
else if section == 1
//create the cell to show loading indicator
...
//here we call loadItems so that there is an indication that something is loading and once loaded we relaod the tableview
self.loadItems()
【讨论】:
其实我以为这个方法行得通,可惜不行,Number of rows抛出了错误,需要在If bock之外添加return语句,CellforRow这个错误抱怨缺少return Cell外If 块,修复了这个问题,但即使是艰难的第 1 节也足够可见并且在桌子的下方,它仍然会触发加载项......【参考方案5】:在 tableview 中使用scrollviewDelegate
是一种很好且有效的方法
只需在您的viewController
中添加UIScrollViewDelegate
在视图控制器中
//For Pagination
var isDataLoading:Bool=false
var pageNo:Int=0
var limit:Int=20
var offset:Int=0 //pageNo*limit
var didEndReached:Bool=false
viewDidLoad(_)
tableview.delegate=self //To enable scrollviewdelegate
覆盖此委托的两个方法
func scrollViewWillBeginDragging(_ scrollView: UIScrollView)
print("scrollViewWillBeginDragging")
isDataLoading = false
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
print("scrollViewDidEndDecelerating")
//Pagination
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool)
print("scrollViewDidEndDragging")
if ((tableView.contentOffset.y + tableView.frame.size.height) >= tableView.contentSize.height)
if !isDataLoading
isDataLoading = true
self.pageNo=self.pageNo+1
self.limit=self.limit+10
self.offset=self.limit * self.pageNo
loadCallLogData(offset: self.offset, limit: self.limit)
【讨论】:
偏移的目的是什么?,self.limit是我假设的pageSize。【参考方案6】:这里是集合视图的示例代码:
var page = 0
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
print("page Num:\(page)")
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
if arrImagesData.count-1 == indexPath.row && arrImagesData.count%10 == 0
getMoreImages(page)
func getMoreImages(page:Int)
//hit api
if api_success == true
if self.page == 0
self.arrImagesData.removeAll()
self.arrImagesData.appendContentsOf(api_data)
self.collectionImages.reloadData()
self.page = self.page + 1
【讨论】:
【参考方案7】:SWIFT 3.0 和 4.0
如果您在 API 请求中发送页码,那么这是在您的应用中实现分页的理想方式。
-
用初始值为 0 和 bool 声明变量 current Page 以检查是否正在加载任何初始值为 false 的列表
var currentPage : Int = 0
var isLoadingList : Bool = false
-
这是获取列表示例的函数:
func getListFromServer(_ pageNumber: Int)
self.isLoadingList = false
self.table.reloadData()
-
这是增加页码并调用 API 函数的函数
func loadMoreItemsForList()
currentPage += 1
getListFromServer(currentPage)
-
这是scrollView滚动时调用的方法
func scrollViewDidScroll(_ scrollView: UIScrollView)
if (((scrollView.contentOffset.y + scrollView.frame.size.height) > scrollView.contentSize.height ) && !isLoadingList)
self.isLoadingList = true
self.loadMoreItemsForList()
附: bool isLoadingList 的作用是防止滚动视图一拖就拉到更多列表到表格视图的底部。
【讨论】:
reloadData()
是否会让用户滚动到顶部?
no reloadData() 不会滚动到顶部,使用tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top , animated: true)
滚动到顶部
我已经完美地实现了您的功能,但是当我滚动得更快或页面计数到 4 或 5 不包含数据时,它给了我index out of bound
错误。如何持续显示数据?【参考方案8】:
现在在 ios10 中添加了一个新协议,这变得更容易了:UITableViewDataSourcePrefetching
https://developer.apple.com/documentation/uikit/uitableviewdatasourceprefetching
【讨论】:
这应该是公认的答案。这是一个关于如何使用这个协议的很好的教程:raywenderlich.com/5786-uitableview-infinite-scrolling-tutorial.【参考方案9】:我尝试了一种使用 willDisplayCell 的方法。但它会在滚动过程中产生不必要的停止,从而使用户体验不佳。 我认为更好的方法是在 scrollViewDidEndDecelerating 委托方法中做到这一点。它在滚动完成时调用,然后才出现新数据。用户看到有新内容,如果他愿意,可以再次滚动。我已经接受了here 的答案,但我使用的是 scrollViewDidEndDecelerating 而不是 scrollViewDidEndDragging。就我而言,它看起来更好。这是我项目中的一些代码。
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)
guard scrollView == tableView,
(scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height,
!viewModel.isLastPeriodicsPage else return
viewModel.paginatePeriodics(tableView.getLastIndexPath())
【讨论】:
你能解释一下这个例子中 viewModel.paginatePeriodics() 是什么吗? 这个方法调用我们的服务器 API 来获取新部分的项目,从给定的索引开始,没什么特别的 此方法存在问题。这取决于用户滚动页面。当屏幕上有空间显示更多项目时,此方法会失败,但它会等待用户滚动触发 API 调用以进行下一页加载。【参考方案10】:制作了一个通用的分页框架:?
https://github.com/eonist/PaginationTable
let table = Table(rowData: [], frame: .zero, style: .plain)
view = table
table.isFetching = true
Table.fetchData(range: table.paginationRange) rowItem in
DispatchQueue.main.async [weak table] in
table?.rowData += rowItem
table?.reloadData()
table?.paginationIndex += Table.paginationAmount // set the new pagination index
table?.isFetching = false
【讨论】:
【参考方案11】:通过UITableViewDelegate,你可以调用函数
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
let lastItem = self.mes.count - 1
if indexPath.row == lastItem
print("IndexRow\(indexPath.row)")
if currentPage < totalPage
currentPage += 1
//Get data from Server
【讨论】:
【参考方案12】://It works fine
func getPageCount(TotalCount : Int) -> Int
var num = TotalCount
let reminder = num % 50
print(reminder)
if reminder != 0
num = TotalCount/50
num = num + 1
else
num = TotalCount/50
return num
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
let TotalPage = self.getPageCount(TotalCount: Int(Datacount)!)
let lastItem = self.mainArr.count - 1
if indexPath.row == lastItem
print("IndexRow\(indexPath.row)")
if self.page < TotalPage-1
self.view_Loader.isHidden = false
self.view_LoaderHeight.constant = 50
self.page += 1
self.YourAPI()
`
【讨论】:
【参考方案13】:API 处理程序是用于网络调用的 api 处理程序,只进行 POST 和 GET 调用。 getNotifications 基本上只是一个带有参数(偏移量和 pageSize)的后调用,并且作为响应有列表。 主要逻辑是根据 willDisplay collectionView 委托中的单元格更改偏移量。如果您有任何问题,请发表评论,很乐意提供帮助。
var isFetching: Bool = false
var offset = 0
var totalListOnServerCount = 20 // it must be returned from server
var pageSize = 10 // get 10 objects for instance
// MARK: - API Handler
private func fetchNotifications()
// return from function if already fetching list
guard !isFetching else return
if offset == 0
// empty list for first call i.e offset = 0
self.anyList.removeAll()
self.collectionView.reloadData()
isFetching = true
// API call to fetch notifications with given offset or page number depends on server logic just simple POST Call
APIHandler.shared.getNotifications(offset: offset) [weak self] (response, error) in
if let response = response
self?.isFetching = false
if self?.offset == 0
// fetch response from server for first fetch
self?.notificationsResponse = response
if self?.refreshControl.isRefreshing ?? false
self?.refreshControl.endRefreshing()
else
// append if already exist ( pagination )
self?.notificationsResponse?.notifications.append(contentsOf: response.notifications)
self?.collectionView.reloadData()
// MARK: - Collection View Delegate
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
guard let anyList = responseFromServer else return
// check if scroll reach last index available and keep fetching till our model list has all entries from server
if indexPath.item == anyList.count - 1 && anyList.count < totalListOnServerCount
offset += pageSize
fetchNotifications()
【讨论】:
【参考方案14】:Swift 5(全面全面的分页解决方案)
用户界面代码: https://github.com/eonist/PaginationTable
数据模型代码: https://github.com/eonist/PaginationService
核心组件:
rowData:此数组将在每个滚动结束事件上增长,直到它从后端 API 加载所有项目 paginationAmount:每个分页周期获取的数量 paginationIndex:当前单元格数量(随着您加载更多数据而增长 isFetching:一个布尔值,让代码知道数据是否已经加载,以避免重复获取等 fetchData:模拟从 remote-api 获取数据 陷阱:示例代码不依赖于后端。它只是使用文件中的数据进行测试,并通过休眠几秒钟来模拟网络调用 该示例使用了一些依赖项以加快此示例的创建速度。但它的基本内容,如 AFNetwork、Json 解析、Autolllayout。所有这些都可以轻松替换 要求:
可以提供项目计数的后端 API 可以返回范围内项目的后端 API(startIndex、endIndex)
【讨论】:
以上是关于Swift tableView 分页的主要内容,如果未能解决你的问题,请参考以下文章
调整 TableView 大小时,Swift 3.0 表格单元格未填充
Swift:tableView 行的高度,其 tableView 单元格具有动态行数的嵌套 tableView
如何使用 MovieDB API Swift 为 TableView 进行分页?