scrollViewDidEndDragging not called in UITableview when data is less - ios

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

Related

UITableView Scroll performing animation high CPU Usage

I have a UITableView that is performing an animation on scroll, basically a sticky header, but instead it adjusts the height constraint of a UIView. I am getting really high CPU usage on scroll. Is there a better way to perform this animation?
Edit: I am not doing anything in the cellForRow at, function that would be causing this. It is definitely the scrollViewDidScroll function.
DidScroll:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.y + tableViewContentInsets
if (lastPoint != nil) {
if (lastPoint! < offset) {
if (portfolioSummaryInitialHeight - offset < portfolioSummaryInitialHeight){
if (portfolioSummaryInitialHeight - offset > 0 ){
portfolioSummaryHeightConstraint.constant = portfolioSummaryInitialHeight - offset
} else {
portfolioSummaryHeightConstraint.constant = 0
}
}
} else {
if (portfolioSummaryInitialHeight - offset < portfolioSummaryInitialHeight){
if (portfolioSummaryInitialHeight - offset > 0 ){
portfolioSummaryHeightConstraint.constant = portfolioSummaryInitialHeight - offset
}
} else {
portfolioSummaryHeightConstraint.constant = portfolioSummaryInitialHeight
}
}
}
lastPoint = offset
}
Additional Variables:
var tableViewContentInsets: CGFloat = 80
var portfolioSummaryInitialHeight: CGFloat = 0 // equals portfolioSummaryInitialHeight at viewDidLoad
var lastPoint: CGFloat?
first of all, I'd advise you to check if you perform any other heavy operations on the main thread e.g data fetch or something similar. I've created a simple test project with a plain table view and a header. Here is the code which does the resizes header. According to the CPU report, it utilizes 8% of CPU in high peaks.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var headerHeightConstr: NSLayoutConstraint! //header's height constraint
#IBOutlet weak var tableView: UITableView!
private var headerHeight: CGFloat = 128.0 ////header's height constraint initial value
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "reuseID") else {
return UITableViewCell(style: .default, reuseIdentifier: "reuseID")
}
return cell
}
}
extension ViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y < 0 {
self.headerHeightConstr.constant += abs(scrollView.contentOffset.y)
} else if scrollView.contentOffset.y > 0 && self.headerHeightConstr.constant >= headerHeight {
self.headerHeightConstr.constant -= scrollView.contentOffset.y
if self.headerHeightConstr.constant < headerHeight {
self.headerHeightConstr.constant = headerHeight
}
}
}
}
This is kinda naive approach but it works, will try to answer your questions if needed.
Check your WillLayoutSubViews and DidLayoutSubViews functions. If you are doing lots of work in those functions then you need to add something like this:
var isScrolling: Bool = false
override func viewDidLayoutSubviews() {
if (isScrolling) {
return
} else {
super.viewDidLayoutSubviews()
}
}
While your tableView is scrolling set isScrolling = true in scrollViewDidEndDragging set isScrolling = false this will ensure that your class will not do excessive work while the tableView is scrolling but insure that it will do the work in other instances. Might not be the best way to do this but it certainly works.

UITableView Pagination willDisplay call again and again swift

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

Scroll on Demand keeps calling

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

Swift tableView Pagination

I have success working tableview with json parsing code. But may have 1000 more item so I need pagination when scrolling bottom side. I don't know how can I do this for my code shown below. For objective-C, there are a lot of examples but for Swift I didn't find a working example.
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()
}
}
For that you need to have server side change also.
Server will accept fromIndex and batchSize in the API url as query param.
let listUrlString = "http://bla.com/json2.php?listType=" + listType + "&t=" + NSUUID().UUIDString + "&batchSize=" + batchSize + "&fromIndex=" + fromIndex
In the server response, there will be an extra key totalItems. This will be used to identify all items are received or not. An array or items fromIndex to batchSize number of items.
In the app side
First loadItem() will be called with fromIndex = 0 and batchSize = 20 (for example in viewDidLoad() or viewWillAppear). removeAll items from privateList array before calling loadItem() for the first time
Server returns an array of first 20 items and totalItems total number of items in the server.
Append the 20 items in privateList array and reload tableView
In tableView:cellForRowAtIndexPath method check if the cell is the last cell. And check if totalItems (form server) is greater than privateList.count. That means there are more items in the server to load
if indexPath.row == privateList.count - 1 { // last cell
if totalItems > privateList.count { // more items to fetch
loadItem() // increment `fromIndex` by 20 before server call
}
}
Question: where is refresh ? will be scrolling ?
Refresh after appending new items in the array when server response received. (step 3)
Scrolling will trigger tableView:cellForRowAtIndexPath for every cell when user scrolls. Code is checking if it is the last cell and fetch remaining items. (step 4)
Sample project added: https://github.com/rishi420/TableViewPaging
SWIFT 3.0 and 4.0
If you're sending the page number in the API request then this is the ideal way for implementing pagination in your app.
declare the variable current Page with initial Value 0 and a bool to check if any list is being loaded with initial value false
var currentPage : Int = 0
var isLoadingList : Bool = false
This is the function that gets the list example:
func getListFromServer(_ pageNumber: Int){
self.isLoadingList = false
self.table.reloadData()
}
This is the function that increments page number and calls the API function
func loadMoreItemsForList(){
currentPage += 1
getListFromServer(currentPage)
}
this is the method that will be called when the scrollView scrolls
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (((scrollView.contentOffset.y + scrollView.frame.size.height) > scrollView.contentSize.height ) && !isLoadingList){
self.isLoadingList = true
self.loadMoreItemsForList()
}
}
P.S. the bool isLoadingList role is to prevent the scroll view from getting more lists in one drag to the bottom of the table view.
The good and efficient way to do it is by using scrollviewDelegate in tableview
Just add UIScrollViewDelegate in your viewController
In view controller
//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
}
Override two methods from this delegate
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)
}
}
}
This is now a little bit easier with the addition of a new protocol in iOS10: UITableViewDataSourcePrefetching
https://developer.apple.com/documentation/uikit/uitableviewdatasourceprefetching
//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()
}
}
}`
By using UITableViewDelegate, u can call the function
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
}
}
}
I needed something similar on a project and my solution was:
1 - create a variable numberOfObjectsInSubArray (initial value 30 or whatever you want)
2 - create a subarray to add a number of objects from your privateList array every time i tap "show more"
let subArray = privateList?.subarrayWithRange(NSMakeRange(0, numberOfObjectsInSubArray))
And use it on
internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return subArray.count
}
3- Whenever you need to show more objects, do:
func addMoreObjectsOnTableView () {
numberOfObjectsInSubArray += 30
if (numberOfObjectsInSubArray < privateList.count) {
subArray = privateList?.subarrayWithRange(NSMakeRange(0, numberOfObjectsInSubArray))
} else {
subArray = privateList?.subarrayWithRange(NSMakeRange(0, privateList.count))
}
tableView.reloadData()
}
I hope it helps
I've tried an approach with willDisplayCell. But it produces unwanted stops during scrolling which makes the user experience not good.
I think a better way is to do it in scrollViewDidEndDecelerating delegate method. It calls when the scroll finishes and only then new data comes. User sees that there is new content and scroll again if he wants. I've taken the answer here but instead of scrollViewDidEndDragging I use scrollViewDidEndDecelerating. It looks just better in my case. Here is some code from my project.
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())
}
Another way of doing this is: You may set a threshold for getting elements while sending request each time:
Lets say you you are fetching 20 elements first time. You will be saving last fetched record id or number for getting list of next 20 elements.
let lastFetchedIndex = 20;
I am assuming that you have already added these records in your myArray. MyArray is the dataSource of tableView. Now myArray is containing 40 objects. I am going to make a list of indexPaths of rows that needs to be inserted in tableView now.
var indexPathsArray = [NSIndexPath]()
for index in lastFetchedIndex..<myArray.count{
let indexPath = NSIndexPath(forRow: index, inSection: 0)
indexPathsArray.append(indexPath)
}
Here I am updating my tableView. Make sure your dataSource i mean your myArray has already been updated. So that it may insert rows properly.
self.tableView.beginUpdates()
tableView!.insertRowsAtIndexPaths(indexPathsArray, withRowAnimation: .Fade)
self.tableView.endUpdates()
Add another section to your tableview, let this section have only 1 row which will be a cell containing an activity indicator, to denote loading.
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()
}
}
here is a sample code for collection view :
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
}
}
API handler is api handler for network call that just do POST and GET calls. getNotifications is basically just a post call with params( offset and pageSize ) and in response there is list.
Main logic is changing offset depending on cell in willDisplay collectionView delegate. Comment if you having any question , happy to help.
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()
}
}
Made a General purpouse pagination framework: πŸŽ‰
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
}
}
Swift 5 (Full comprehensive pagination solution)
The UI code:
https://github.com/eonist/PaginationTable
The Data Model code:
https://github.com/eonist/PaginationService
Core components:
rowData: This array will grow on each scroll-ended-event until it has loaded all items from backend-API
paginationAmount: The amount to fetch on each pagination cycle
paginationIndex: The current amount of cells (this grows as you load more data
isFetching: A boolean that lets the code know if data is already loading or not, to avoid double fetching etc
fetchData: Simulates getting data from remote-api
Gotchas:
The example code is not reliant on a backend. It simply tests with data from a file and simulates network calls by sleeping for some seconds
The example uses some dependencies in order to speed up the creation of this example. But its basic stuff like AFNetwork, Json parsing, Autollayout. All of which could easily be substituted
Requirements:
Backend-API that can provide the count of items
Backend-API that can return items for a range (startIndex, endIndex)

Why my "pull down to load data" stuck at page 2 without loading anymore data

Is there a theory about reloading new data by appending old data with new from json?I am having problem with solving that case.I used this infinite uitableview to reload more data from my api json string.My Api give me back the results base on page no.
My json header include "totalCount","currentPage" & "toPage" as it was describe in the below code."totalCount" means the total results that i am going to get.But,it gives me 15 results per page.So,if "totalCount" is 636.I have to go 43 pages(toPage).
Here is my code.
ViewController.swift
import UIKit
class ViewController: UITableViewController,AuctionAPIProtocol{
var currentPage = 1
var currentCount = 0
var toPage = 0
var totalCount = 0
var api : AuctionAPI?
let cellId = "cell"
#IBOutlet var tableViewFooter:MyFooter! //Table Footer which was activity indicator
var items:[AuctionModel] = []//An empty array where items gonna store
var newItems:[AuctionModel] = []
var loading = false // Loading State for activity indicator
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: cellId)
self.tableViewFooter.hidden = true
api = AuctionAPI(delegate: self)
api!.searchAuctionLatestFor("null",lotId: "1",page: "1")
}
//TableView Delegate and Datasource
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellId, forIndexPath: indexPath) as UITableViewCell
let item = items[indexPath.row]
cell.textLabel?.text = item.vendorName.capitalizedString+" "+item.modelName.capitalizedString+" "+item.year
return cell
}
override func scrollViewDidScroll(scrollView: UIScrollView) {
// 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) <= 40.0 {
loadSegment(currentPage, count: items.count)
}
}
func loadSegment(currentP:Int, count:Int) {
//println("load segment current page: \(currentPage)")
api = AuctionAPI(delegate: self)
if (!self.loading) {
self.setLoadingState(true)
println("CP\(currentP)")
println("TP\(count)")
if currentPage < toPage{
println("Enter Condition")
var times = 0
api!.searchAuctionLatestFor("null",lotId: "1",page: String(++currentPage))
println("Current Page After API Call : \(currentPage)")
}
else if currentPage > toPage {
setLoadingState(false)
}
}
else{
println("Not Loading")
}
}
// Loading Progress at Table View Footer condition (disabling the table view footer which include loading indicator or not)
func setLoadingState(loading:Bool) {
self.loading = loading
self.tableViewFooter.hidden = !loading
}
func didReceiveAPIResults(results: NSDictionary) {
var resultsArr: NSArray = results["body"] as NSArray
//fix
dispatch_async(dispatch_get_main_queue(), {
self.newItems = AuctionModel.latestWithJSON(resultsArr)
println("Current Auction Items : \(self.newItems.count)")
//println("Page: \(self.currentPage) Items \(self.items)")
if self.currentPage > 1 {
for item:AuctionModel in self.newItems {
self.items.append(item)
}
}
else{
self.items = self.newItems
}
self.currentCount = self.items.count
println("After Auction Items : \(self.currentCount)")
self.tableView?.reloadData()
//Status bar network activity α€€α€­α€― ပိတ်​ရန်/ဖွင့်​ခဲ့​ရင်
//UIApplication.sharedApplication().networkActivityIndicatorVisible = false
self.tableView?.hidden = false
})
}
func doSearchMoreAPIResults(results: JSON){
if results != nil{
totalCount = results["header"]["totalCount"].intValue
currentPage = results["header"]["currentPage"].intValue
toPage = results["header"]["toPage"].intValue
}
println("totalCount : \(totalCount)")
println("currentPage : \(currentPage)")
println("toPage : \(toPage)")
}
func didNotReceiveAPIResults(results: Bool){
var connectionResult : Bool = results
}
}
AuctionAPI.swift
import Foundation
protocol AuctionAPIProtocol{
func didReceiveAPIResults(results: NSDictionary)
func didNotReceiveAPIResults(results: Bool)
func doSearchMoreAPIResults(results:JSON)
}
class AuctionAPI{
var delegate: AuctionAPIProtocol
var urlParameterStringController:URLParameterStringController!
init(delegate: AuctionAPIProtocol){
self.delegate=delegate
}
func post(path:String,params:Dictionary<String,String>){
//Parameter Parts.....
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
.....
self.delegate.didReceiveAPIResults(jsonData)
self.delegate.doSearchMoreAPIResults(json)
})
task.resume()
}
func searchAuctionLatestFor(token:String,lotId:String,page:String){
.....
post(urlPath,params: params)
}
}
I updated the code,and now i am stuck at "Loading" when i scroll down after getting 30 results.Actually it should load 636 results.
This is my console output.
This is my simulator which was at page 2 and stuck at loading.Fig
No more duplicate results now,but why it dont load more data and stuck at page 2 when i scroll down to load more.
The reason why i am stucking at Page 2 with loading was,I dont set the "setLoadingState()" to false after i get the results from "didReceiveAPIResults()".So after appending the results into items Array,doing self.setLoadingState(false) solved the problem and it will enter to if(!self.loading) condition when user pull up to load more data from another page.
Thank you.I will not update the answer because i want all who like me to find out.
In didReceiveAPIResults you are setting self.items and hen appending to it so you are deleting the old data and then duplicating it. You should simply be appending the new results onto the existing list.

Resources