I am trying to implement pagination in UITableView after searching this question on stackoverflow and google i got many method but facing same problem in all the solution, in willDisplay last cell method calling again and again so pagination does not work it load data in loop before i move to last cell please guide me self.isGettingMoreData is false when all data is fetched.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if !self.isGettingMoreData && indexPath.row >= (self.datasource.count - 1) {
self.isGettingMoreData = true
loadmore(pagenum)
}
}
and i am using scrolview like this but in this way i need to drag at the end to load more i don't want to like this way
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
//Bottom Refresh
if scrollView == table{
if ((scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height)
{
if !isLoading{
isLoading = true
loadmore()
To implement pagination in a table view or a collection view you can put your code in scrollViewDidScroll method. Here is a sample code that I use:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if moreResultsAvailable {
if let lastVisibleIndexPath = self.collectionView.indexPathsForVisibleItems.last {
if let dataCount = dataArray.count {
let dif = abs(dataCount - lastVisibleIndexPath.row)
if dif == 0 {
pageNo += 1
fetchMoreData()
}
}
}
}
}
Related
I am using pagination to show manage multiple data. Pagination works on both sides top and bottom. For this, I am using below code to call API. But I have faced the issue when data is not greater then tableView height. In this case scrollViewDidEndDragging method not called. So please tell me how to solve this problem. below code is working fine when data is greater then tableView height.
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if(scrollView.panGestureRecognizer.translation(in: scrollView.superview).y > 0) {
print("up")
if workerInfo.count > 0 {
let topVisibleIndexPath:IndexPath = self.tableView.indexPathsForVisibleRows![0]
if topVisibleIndexPath.row == 0 && startCount != 0 && !isDataLoading {
isDataLoading = true
startCount = startCount - requiredCount
self.callAPI(isCallFromPagination: true)
}
}
}
else {
print("down")
if workerInfo.count > 0 {
let arrayOfVisibleItems = tableView.indexPathsForVisibleRows?.sorted()
let lastIndexPath = arrayOfVisibleItems!.last
// print("Array: ", arrayOfVisibleItems)
print("Last IndexPath: ", lastIndexPath as Any)
if lastIndexPath?.row == workerInfo.count - 1 && !isDataLoading {
isDataLoading = true
startCount = startCount + requiredCount
self.callAPI(isCallFromPagination: true)
}
}
}
}
Can you please check this properties.In my code its works perfectly.
extension ViewController: UIScrollViewDelegate {
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
print("Called")
}
}
// Support Pagination
extension TableViewController: UIScrollViewDelegate {
// Set Pagination Trigger before DataSoruce remaining 6 items display
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// Load More
setupPaginationAt(indexPath)
}
// If we reached at the end, check again if anything to load
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
let bottomEdge = scrollView.contentOffset.y + scrollView.frame.size.height
if (bottomEdge >= scrollView.contentSize.height) {
// We are at the end
setupPaginationAt(nil)
}
}
func setupPaginationAt(_ indexPath: IndexPath?) {
// If any network calls on the way, we must leave - Declare a Bool variable and manipulate the value of it
if isLoading {
return
}
// Validation
guard let _dataSource = YourDataSource else {
print("DataSource found empty")
return
}
// Config Pagination Call
func execute() {
// Get Current Pagination - Hope you have page index's from API
if let currentPage = dataSource.pageIndex, let lastPage = dataSource.totalPages {
if currentPage < lastPage {
let nextPage = currentPage + 1
loadData(at: nextPage)
}
}
}
// Check InBetween or End
if let _indexPath = indexPath {
if _indexPath.row == _dataSource.count - 6 {
execute()
}
} else {
// Assume End
execute()
}
}
}
This solution should work.
You can keep your table view bounce and scrollview bounce True. then scrollViewDidEndDragging method called
I am trying to implement scroll on demand call. Until I scroll at the bottom of the screen, it never calls doPaging() method which is good and then downloads another batch of dataset( 20 more items). However when it reaches at the bottom of screen first time and keeps calling even small scroll to the bottom.
I wonder what I am missing in the following implementation.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
{
if indexPath.row == self.products.count - 1 && !isWating {
isWating = true
self.pageNumber += 1
self.doPaging()
}
}
func doPaging()
{
fetchProducts(page: pageNumber , completion: { success in
if let products = success as? Products
{
// keeps calling
DispatchQueue.main.async {
self.products.append(contentsOf:products)
self.isWating = false;
self.productTableView.reloadData()
}
}
})
}
Use this method. This will work surely
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
//Bottom Refresh
if ((scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height) {
if !isWating {
isWating = true
self.doPaging()
}
}
}
I have a chat messaging app where it loads my messages from earliest to latest, then auto scrolls to the bottom so the user sees the latest.
I'm only loading 25 messages, then when the user scrolls to the top, I'd like to upload the next 25 messages. It's proving to be really tricky and ending up in an infinite loop.
Here's what I'm doing:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard indexPath.row == 1 else { return }
fetchNextMessages(with: lastReference: lastMessage.reference)
}
Before the table finished scrolling to the bottom, it started fetching the next set which is wrong. So I tried this:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView.contentOffset.y <= 0 else { return }
fetchNextMessages(with: lastReference: lastMessage.reference)
}
However, same issue. Any better way to handle this?
You should note that this method is being called multiple times , you should make fetchingNow = false after you reload the table with new data , besides when you load the new content don't make the scroll to the oldest ones and leave the user to scroll up to see remaining
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView.contentOffset.y <= 0 else { return }
if(!fetchingNow)
{
fetchNextMessages(with: lastReference: lastMessage.reference)
}
}
I am working on chat application. In my app load earlier message feature implemented which is not smooth and accurate like whatsapp.
I am using UITableview for chat listing and fetching more data using
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y >= 0 && scrollView.contentOffset.y <= 50{
print(" scrollViewDidScroll Load Earlier start- \(Utils.stringFromNSDate(Date(), inMillisec: true, useUTC: true)!)")
if !self.messageFetcher.isHideLoadMoreDisplay{
if self.messageFetcher.arrayOfallChatMessagesData.count > 0 && !isCheckLoading{
self.isCheckLoading = true
let message = self.messageFetcher.arrayOfallChatMessagesData[0]
self.messageIdForMessageDisplay = (message.chatMessageId )
self.loadMoreDataLoad(threadId: self.messageFetcher.chatThreadId, isloadFromServer: false)
}
}
print(" scrollViewDidScroll Load Earlier end- \(Utils.stringFromNSDate(Date(), inMillisec: true, useUTC: true)!)")
}
}
So, Which is better way to achieve load earlier with smoothness same as like whatspp Application.
You should always load your data in CellForWowatIndexpath method, and also use pagination and load more data when that last cell is displayed. You can call load more method on cellWillDisplay method. And last but not least if you are loading images use SDWebimage to load the image so that the scrolling becomes smooth
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// load dats here
return cell
}
Reload the data when last cell is visible
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastItem = self.YourArray.count - 1
if indexPath.row == lastItem
{
//call load more data method
}
else{
return
}
}
I wonder if tableview has any built-in function to add infinite scroll/pagination.
Right now my VC looks like this:
var data: JSON! = []
override func viewDidLoad() {
super.viewDidLoad()
//Init start height of cell
self.tableView.estimatedRowHeight = 122
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.delegate = self
self.tableView.dataSource = self
savedLoader.startAnimation()
//Load first page
loadSaved(1)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("aCell") as! SavedTableViewCell
let info = data[indexPath.row]
cell.configureWithData(info)
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("WebSegue", sender: indexPath)
tableView.deselectRowAtIndexPath(indexPath, animated: false)
}
I fetch my data using loadSaved(1) by giving the function the current page I want to load. The function makes a API request using alomofire then populate the var data: JSON! = [] with the data that should be displayed
So what I want to do is when I scroll to the bottom of the tableview loadSaved(2) should be called loading more data into the tableview
Looks like amar’s answer might be a better solution now, but here’s my answer from 2017 anyway:
The UITableViewDelegate has a tableView(_:willDisplay:forRowAt:) instance method which "tells the delegate the table view is about to draw a cell for a particular row."
In your case I would use it something like this:
override open func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == data.count-1 { //you might decide to load sooner than -1 I guess...
//load more into data here
}
}
Depending on your code, you may need some checks around this to ensure you don't end up in an infinite loop if you've loaded all your data...
No, the UITableView has not any built-in function to achieve the infinite scroll or load on-demand cells like you want. What you can use is the function scrollViewDidScroll(_:) in the UIScrollViewDelegate implemented by default in the UITableView and in this way know when the user scroll more than the original height defined in the UITableView.
For example like in this code:
var indexOfPageToRequest = 1
override func scrollViewDidScroll(scrollView: UIScrollView) {
// calculates where the user is in the y-axis
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if offsetY > contentHeight - scrollView.frame.size.height {
// increments the number of the page to request
indexOfPageToRequest += 1
// call your API for more data
loadSaved(indexOfPageToRequest)
// tell the table view to reload with the new data
self.tableView.reloadData()
}
}
To achieve the result of add the rest of the elements at the end of the UITableView you should add the new elements to the data source, in your case data inside your function loadSaved(numberOfPage).
I hope this help you.
All the above answers are correct but for iOS 10 and above we have a very nice
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath])
This is a prefetch delegate which needs to be set
tableView.prefetchDataSource = self
RayWeinderlich has a nice tutorial on the topic. Since Rays is a dependable site i am not posting code here
I have modified Victor's answer and used it as ,
var indexOfPageRequest = 1
var loadingStatus = false
func loadData(){
if !loadingStatus{
loadingStatus = true
viewModel.getData(pageIndex: indexOfPageRequest)
}
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
// calculates where the user is in the y-axis
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if offsetY > contentHeight - scrollView.frame.size.height {
// increments the number of the page to request
indexOfPageRequest += 1
// call your API for more data
loadData()
// tell the table view to reload with the new data
self.tableView.reloadData()
}
}
Reset loadingStatus to true when you receive data. Without checking if the view was already loading more data, the tableview was flickering.
Ravi's answer looks good. But as he pointed out in the end, the tableView flickers a lot if you use scrollViewDidScroll(_ scrollView: UIScrollView)
This is because you are trying to reload tableView every time you are scrolling the tableView.
Instead you could use scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) delegate method to determine whether you have scrolled enough and have reached almost the end of the tableView.
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
if offsetY > contentHeight - scrollView.frame.size.height {
indexOfPageRequest += 1
loadData()
self.tableView.reloadData()
}
}
Above Ans are also right, but may be some one help out this code also.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath){
if(indexPath.row == self.arryOfData.count-1){
if(self.pageNumber <= self.resPgNumber){
if(remaining != 0){
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
spinner.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(44))
spinner.startAnimating()
tableView.tableFooterView = spinner
tableView.tableFooterView?.isHidden = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.flgActivity = false
self.getActiveOrdersList()
}
}
else{
tableView.tableFooterView?.removeFromSuperview()
let view = UIView()
view.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(5))
tableView.tableFooterView = view
tableView.tableFooterView?.isHidden = true
}
}
else{
tableView.tableFooterView?.removeFromSuperview()
let view = UIView()
view.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(5))
tableView.tableFooterView = view
tableView.tableFooterView?.isHidden = true
}
}
else{
tableView.tableFooterView?.removeFromSuperview()
let view = UIView()
view.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(5))
tableView.tableFooterView = view
tableView.tableFooterView?.isHidden = true
}
}
There are number of ways we can do this. The essense of all different ways, is to load next set of data when user scroll to last. I have implemented it via adding an extra special cell at the end of tableView and when that cell gets loaded in willDisplay cell: forRowAtIndexPath: which triggers next set of fetching of data.
Athough this is simple to implement but in larger apps at times we need to implement it many places. To avoid this, I wrote a small framework which is non-intrusive and can be easyly integrated.