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
}
}
Related
I'm trying to load the components using the default lazy load implementation of Apple: Basically I'm loading the components from my server in the cellForRowAtIndexPath method. Before the entire collectionView loads, I pre-load some of the components by calling them from the server. The thing is that if I pre-load all the elements, it takes a long time ti finish loading the elements.
The problem I'm facing is that, when I scroll down, it gets a little jumpy because it's waiting for the components to load. What chances can I do to improve the load?
Here is the code I'm using to load the elements as the user scrolls:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
do {
if expComponents[indexPath.row].items == nil {
let componentItemsResponse = try `await`(interactor!.lazyloadItems(with: expSection!.id!, componentId: expComponents[indexPath.row].id!))
expComponents[indexPath.row].items = componentItemsResponse.result
}
let cell: ComponentTableViewCell? = getCell(
forIndexPath: indexPath,
componentType: expComponents[indexPath.row].componentType!)
if let mCell = cell {
mCell.configure(withComponent: self.expComponents[indexPath.row], delegate: self)
return mCell
}
} catch {
print(error)
}
return UITableViewCell()
}
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 = []
}
}
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()
}
}
}
}
}
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 have an app that pulls objects from Firebase, then displays them in a table. I've noticed that if I delete 5 entries (this is about when I get to the reused cells that were deleted), I can't delete any more (red delete button is unresponsive) & can't even select the cells. This behavior stops when I comment out override func prepareForReuse() in the TableViewCell.swift controller. Why???
The rest of the app functions normally while the cells are just unresponsive. Weirdly, if I hold one finger on a cell and tap the cell with another finger, I can select the cell. Then, if I hold a finger on the cell and tap the delete button, that cell starts acting normally again. What is happening here??? Here is my code for the table & cells:
In CustomTableViewCell.swift >>
override func prepareForReuse() {
// CELLS STILL FREEZE EVEN WHEN THE FOLLOWING LINE IS COMMENTED OUT?!?!
cellImage.image = nil
}
In ViewController.swift >>
override func viewDidLoad() {
super.viewDidLoad()
loadUserThings()
}
func loadUserThings() {
ref.child("xxx").child(user!.uid).child("yyy").queryOrdered(byChild: "aaa").observe(.value, with: { (snapshot) in
// A CHANGE WAS DETECTED. RELOAD DATA.
self.arr = []
for tempThing in snapshot.children {
let thing = Thing(snapshot: tempThing as! DataSnapshot)
self.arr.append(thing)
}
self.tableView.reloadData()
}) { (error) in
print(error)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
let cellData = arr[indexPath.row]
...
// SET TEXT VALUES OF LABELS IN THE CELL
...
// Setting image to nil in CustomTableViewCell
let imgRef = storageRef.child(cellData.imgPath)
let activityIndicator = MDCActivityIndicator()
// Set up activity indicator
cell.cellImage.sd_setImage(with: imgRef, placeholderImage: nil, completion: { (image, error, cacheType, ref) in
activityIndicator.stopAnimating()
delay(time: 0.2, function: {
UIView.animate(withDuration: 0.3, animations: {
cell.cellImage.alpha = 1
})
})
})
if cell.cellImage.image == nil {
cell.cellImage.alpha = 0
}
// Seems like sd_setImage doesn't always call completion block if the image is loaded quickly, so we need to stop the loader before a bunch of activity indicators build up
delay(time: 0.2) {
if cell.cellImage.image != nil {
activityIndicator.stopAnimating()
cell.cellImage.alpha = 1
}
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// instantly deselect row to allow normal selection of other rows
tableView.deselectRow(at: tableView.indexPathForSelectedRow!, animated: false)
selectedObjectIndex = indexPath.row
self.performSegue(withIdentifier: "customSegue", sender: self)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
print("should delete")
let row = indexPath.row
let objectToDelete = userObjects[row]
userObjects.remove(at: row)
ref.child("users/\(user!.uid)/objects/\(objectToDelete.nickname!)").removeValue()
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
if (self.tableView.isEditing) {
return UITableViewCellEditingStyle.delete
}
return UITableViewCellEditingStyle.none
}
A few things. For performance reasons, you should only use prepareForReuse to reset attributes that are related to the appearance of the cell and not content (like images and text). Set content like text and images in cellForRowAtIndexPath delegate of your tableView and reset cell appearance attributes like alpha, editing, and selection state in prepareForReuse. I am not sure why it continues to behave badly when you comment out that line and leave prepareForReuse empty because so long as you are using a custom table view cell an empty prepareForReuse should not affect performance. I can only assume it has something to do with you not invoking the superclass implementation of prepareForReuse, which is required by Apple according to the docs:
override func prepareForReuse() {
// CELLS STILL FREEZE EVEN WHEN THE FOLLOWING LINE IS COMMENTED OUT?!?!
super.prepareForReuse()
}
The prepareForReuse method is only ever intended to do minor cleanup for your custom cell.