PFQueryTableViewController infinite-scroll swift - ios

So what I'm trying to achieve is basically to use PFQueryTableViewController with infinite scrolling. So far, I've set paginationEnabled to false because I want to hide the "Show more" cell that appears. And I have also set the amount of posts to be loaded each "page" to be 10.
self.paginationEnabled = false
self.objectsPerPage = 10
Now for the infinite scrolling. I have set up the following function
override func scrollViewDidScroll(scrollView: UIScrollView) {
if (scrollView.contentSize.height - scrollView.contentOffset.y < (self.view.bounds.size.height)) {
if !self.loading {
self.loadNextPage()
}
}
}
The problem is that when it reaches the end (the last database rows) it just keeps looping the previous tableView cells. I need a way to check if there are new database rows to fetch or not. Any ideas?

I suggest you an idea:
Your objectsPerPag = 10. But when you query you will query 11. If you actual query return 11 objects so this result mean that continue have another object. You will continue loadmore. And when you return you will remove last object of this result query and you will get 10 item as you want.
Hope my idea can help you!

Okay, so the way I did this was that I set paginationEnabled to true instead of false.
self.paginationEnabled = true
Then I followed up the the following function
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row + 1 > self.objects?.count {
return 0
}
let height = super.tableView(tableView, heightForRowAtIndexPath: indexPath)
return height
}
What this does is that it checks for the "Load more" cell. If there are 10 objects then the 11th would be the "Load more" cell. Therefore indexPath.row + 1. It then sets its height to 0 which hides it.

Related

UITableView with Full Screen cells - pagination breaks after fetch more rows

My app uses a UITableView to implement a TikTok-style UX. Each cell is the height of the entire screen. Pagination is enabled, and this works fine for the first batch of 10 records I load. Each UITableViewCell is one "page". The user can "flip" through the pages by swiping, and initially each "page" fully flips as expected. However when I add additional rows by checking to see if the currently visible cell is the last one and then loading 10 more rows, the pagination goes haywire. Swiping results in a partially "flipped" cell -- parts of two cells are visible at the same time. I've tried various things but I'm not even sure what the problem is. The tableView seems to lose track of geometry.
Note: After the pagination goes haywire I can flip all the way back to the first cell. At that point the UITableView seems to regain composure and once again I'm able to flip correctly through all of the loaded rows, including the new ones.
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// Pause the video if the cell is ended displaying
if let cell = cell as? HomeTableViewCell {
cell.pause()
}
if let indices = tableView.indexPathsForVisibleRows {
for index in indices {
if index.row >= self.data.count - 1 {
self.viewModel!.getPosts()
break
}
}
}
}
In order to create a "Tik Tok" style UX, I ended up using the Texture framework together with a cloud video provider (mux.com). Works fine now.
I was facing the same issue and as I couldn't find a solution anywhere else here's how I solved it without using Texture:
I used the UITableViewDataSourcePrefetching protocol to fetch the new data to be inserted
extension TikTokTableView: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
viewModel.prefetchRows(at: indexPaths)
}
}
prefetchRows will execute the request if the visible cell is the last one, as in my case
func prefetchRows(at indexPaths: [IndexPath]) {
if indexPaths.contains(where: isLastCell) {
getPosts(type: typeOfPosts, offset: posts.count, lastPostId: lastPost)
}
}
private func isLastCell(for indexPath: IndexPath) -> Bool {
return indexPath.row == posts.count - 1
}
I have a weak var view delegate type TikTokTableViewDelegate in my view model to have access to a function insertItems implemented by my TikTokTableView. This function is used to inform the UITableView where to insert the incoming posts at
self.posts.append(contentsOf: response.posts)
let indexPathsToReload = self.calculateIndexPathToReload(from: response.posts)
self.view?.insertItems(at: indexPathsToReload)
private func calculateIndexPathToReload(from newPosts: [Post]) -> [IndexPath] {
let startIndex = posts.count - newPosts.count
let endIndex = startIndex + newPosts.count
print(startIndex, endIndex)
return (startIndex..<endIndex).map { IndexPath(row: $0, section: 0) }
}
and this is the insertItems function implemented in TikTokTableView and here is the key: If we try to insert those rows, the pagination of the table will fail and leave that weird offset, we have to store the indexPaths in a local property and insert them once the scroll animation has finished.
extension TikTokTableView: TikTokTableViewDelegate {
func insertItems(at indexPathsToReload: [IndexPath]) {
DispatchQueue.main.async {
// if we try to insert rows in the table, the scroll animation will be stopped and the cell will have a weird offset
// that's why we keep the indexPaths and insert them on scrollViewDidEndDecelerating(:)
self.indexPathsToReload = indexPathsToReload
}
}
}
Since UITableView is a subclass of UIScrollView, we have access to scrollViewDidEndDecelerating, this func is triggered at the end of a user's scroll and this is the time when we insert the new rows
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if !indexPathsToReload.isEmpty {
tableView.insertRows(at: indexPathsToReload, with: .none)
indexPathsToReload = []
}
}

Infinite scrolling for scrolling up a tableView?

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

Some unwanted UIImageViews disappear while trying to set them as borders of UITableView

What I am trying to do is to replace borders(solid lines by default, of course) by small dots. I want the size of the dots to be 6 by 6.
Making borders invisible is a simple thing to do; I just set separators as 'None' in the attribute inspector.
But there was no field to set images or other objects as separators. To solve this problem, I splitted the dot into up and down pieces, assigned them in UIImageViews, and located the upper one on the bottom and the lower one on the top.
Then in the function tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath), I got the lower image in the first row and the upper image in the last row invisible.
What make the problem is that some unwanted dots disappear, while the dots I intended to change are properly gone away. Furthermore they come back or go away when the tableView is scrolled. The codes and the screenshot are below.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "reuseIdentifier"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MyListTableViewCell
//each element in datas[] has lowercase alphabets, a from q.
let array = datas[indexPath.row]
//data is a UIButton, which is the only component in each row besided those dots
cell.data.setTitle(array, forState: .Normal)
//this is where I typed to handle the unwanted dots on the very top and bottom
if indexPath.row == 0{
cell.lowerHalf.hidden = true
}
else if indexPath.row == datas.count - 1{
cell.upperHalf.hidden = true
}
///////////////////
return cell
}
When I scrolled tableView up and down side, it changed to this:
What makes the dots which shouldn't disappear go away? Or is their a better way to set images as borders?
+In the way iSashok suggested, things didn't change unfortunately. I applied this code right after the declaration; and then I moved to right before the return statement. Both did not work.
cell.clipsToBound = false
Your cell is getting reuse so you need to change your if condition of cellForRowAtIndexPath like this
if indexPath.row == 0 {
cell.lowerHalf.hidden = true
cell.upperHalf.hidden = false
}
else if indexPath.row == datas.count - 1{
cell.lowerHalf.hidden = false
cell.upperHalf.hidden = true
}
else {
cell.lowerHalf.hidden = false
cell.upperHalf.hidden = false
}
Try to modify for your cell clipsToBounds property in
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
like below
cell.clipsToBounds = false;
and for contentView the same
cell.contentView.clipsToBounds = false;
Why don't you use UITableViewHeaderFooterView? Simply create your view with that dot image in centre and return that view from viewForFooterInSection.

Paging Data in a UITableView

I am currently trying to create a UITableView that loads data as a user scrolls through it.
Basically, I have a data source with a lot of records. Way more than is feasible to load all at once. So I am querying 50 records at a time.
The problem lies in the fact that the user will be able to jump to the middle of this list via letters at the right. For example, if they press 'M' I load 50 records starting at 'M' into the list. Scrolling down will navigate through the M's and eventually the N's, O's, etc.
Of course, appending data to the bottom of the list is common and I was able to do this easily. I am having trouble finding reference or if it is done at all of appending 50 records to the top of the list as a user scrolls up.
For example, if the user hits 'M'. they should be able to scroll up and start seeing L's. I can append data to the beginning of the list, but the problem lies in the continuation of the scrolling list.
As of now, I can not get it working without a jump of the list or a complete stop.
Can anyone point me to someone who has done this cleanly?
OK, so I figured out the answer to my own question. Sorry for the post but maybe it will help someone else because I failed to find an exact solution to my answer else where, at least when it came to Swift.
First I loaded 50 records that started with 'M'. The JSON data would return the row number of these records. I created Int variables 'begin' and 'end' which held on to the range of rows I had loaded into my array. I set the numberOfRowsInSection to the 'end' of my range. This number was significantly more than the amount of rows I had loaded in my array, so I had to finagle how rows were loaded based on that array. Simple code snippet:
var begin:Int?
var end:Int = 0
func onDataLoad() {
data = data + DataHandler.data!
begin = data[0].Row
end = begin! + 50
tableView.reloadData()
let count = tableView.indexPathsForVisibleRows!.count - 2
let indexPath = NSIndexPath(forRow: begin! + count, inSection: 0)
tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.None, animated: false)
loading = false
}
func onBackwardData(evt:Event) {
data = DataHandler.data! + data
begin = begin! - 50
tableView.reloadData()
loading = false
}
func onForwardData() {
end = end + 50
data = data + DataHandler.data!
tableView.reloadData()
loading = false
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return end
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("UserCell", forIndexPath: indexPath) as! PulseListTableViewCell
let cellrow = indexPath.row - begin!
if cellrow >= 0 {
cell.name.text = data[cellrow].Name
}
return cell
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
row = indexPath.row
let count = tableView.indexPathsForVisibleRows!.count + 5
if(indexPath.row >= end - 5 && !loading)
{
loading = true
DataHandler.getUsers(end)
}
else if(indexPath.row <= begin! + count && begin! > 0 && !loading)
{
loading = true
var offset = begin! - 50
if offset < 0 {
offset = 0
}
DataHandler.getUsers(offset)
}
}
This code is crude and has some hard coded things that would need to be adjusted to handle getting to the very beginning or end of the list but the idea is there. Sorry if this is a totally obvious solution/issue in the iOS world but I'm new and thought it might help.

How to hide content on specific cells only, despite using dequeueReusableCell?

I would like to hide some elements in a custom cell when we overpass a specific number of row. I added more row than the ones visible, because I needed to scroll until the last row without the bouncing effect. But now I have more cells, and I don't need the cells after row > 13.
I tried to setNeedsDisplay the cell with a if else, but the dequeue... method has a bad effect on the cells, when I scroll up, back to the previous cells, they don't have the texts anymore, like the row > 13. Is there a way to use the dequeue method, and let the content for the rows < 13, and remove the content for the rows > 13 ?
Here is some code :
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var identifier = ""
if tableView == self.tableView{
identifier = "MyCell"
let cell = tableView.dequeueReusableCellWithIdentifier(identifier) as MyCell
if indexPath.row < 14 {
cell.showContent = true
cell.label.text = "test\(indexPath.row)"
}
else {
cell.showContent = false
cell.label.text = ""
cell.addItem.text = ""
}
cell.setNeedsDisplay()
return cell
}
//MyCell
override func drawRect(rect: CGRect) {
if !showContent {
label.text = ""
addItem.text = ""
}
else {
let path = UIBezierPath()//custom separator that should not be drawn for row > 13
Thanks
You shouldn't modify the text this way in drawRect. You already modified the labels in cellForRow. That's all you need.
That said, this isn't how I would do it. I'd probably create a different cell with its own identifier for empty cells. That way they can be really simple and you don't have to do things like cell.setNeedsDisplay() to get rid of the separator line. So in cellForRow, just return one kind of cell for data rows, and a different kind of cell for empty rows. There's no rule that says all the cells have to be the same class.

Resources