Scrolling bottom in tableview and performance - ios

I need to scroll to bottom. I have a chat app like whatsapp. So when view appears table view should show last row. I am achiving this with following line and works nice.
tableView.setContentOffset(CGPointMake(0, CGFloat.max), animated: false)
Also I need to scroll to bottom when keyboard appears. I am using Auto Layout and above line is not working. For to do this i am using following line:
func scrollToLastRow(animated: Bool) {
if self.numberOfRowsInSection(0) > 0 {
self.scrollToRowAtIndexPath(NSIndexPath(forRow: self.numberOfRowsInSection(0) - 1, inSection: 0), atScrollPosition: .Bottom, animated: animated)
}
}
This is a extension for Tableview.
This solution is working fine when there is no too much message. Then I tried with 5000 messages (so tableview have 5000 rows, but i am paging them) And when keyboard appears i see cpu usage is %98-100. I think the second code is problem for pagination, it causes loading every message to ram and my app freezes and receiving ram warning.
How to scroll to bottom without any performance issue?

If you have pagination, you can try to only load your current page as well as the final page, assuming you have 20 messages in each page, in this case your table have 40 rows only. Then you can use your function:
func scrollToLastRow(animated: Bool) {
if self.numberOfRowsInSection(0) > 0 {
self.scrollToRowAtIndexPath(NSIndexPath(forRow: self.numberOfRowsInSection(0) - 1, inSection: 0), atScrollPosition: .Bottom, animated: animated)
}
}

try this method :
let delay = 0.1 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
let numberOfSections = self.tableView.numberOfSections
let numberOfRows = self.tableView.numberOfRowsInSection(numberOfSections-1)
if numberOfRows > 0 {
let indexPath = NSIndexPath(forRow: numberOfRows-1, inSection: (numberOfSections-1))
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: true)
}
})

If your are creating IM app. I recommend you reverse the tableView.
So the first row appears at bottom, and don't needs scrolling at beginning anymore.
Here is a cocoapod could help: https://github.com/marty-suzuki/ReverseExtension
If you stil want to scroll to specific row
implement the UIScrollViewDelegate.scrollViewDidEndScrollingAnimation.
and goes like this.
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
if scrollView.contentSize.height > lastContentHeight {
self.tableView.scrollToBottom()
self.lastContentHeight = scrollView.contentSize.height
}
}

Related

CollectionView scroll with button tap scrollToItem not working

I currently have a login and signup buttons labeled as 1 and 2. And underneath that, I have a collectionView with two cells (one for the login UI and one for register UI). When I click on the signup button(Label 2), I want to move the collectionView to the next cell.
I tried using the scrollToItem, but it does not do anything. Anyone know why?
#objc private func didTapRegisterButton() {
let i = IndexPath(item: 1, section: 0)
self.onboardingCollectionView.scrollToItem(at: i, at: .right, animated: true)
}
Figured it out. My collectionView had isPagingEnabled property as true. The scrollToItem doesn't work if that is set to true.
If you want to scroll between two cells on button click then first you need to uncheck(disable) Scrolling Enabled property and check(enable) Paging Enabled property of collectionView. (With this approach user can't scroll manually with swipe)
On button first action,
if self.currentIndexPath == 0 {
return
} else {
self.currentIndexPath += 1
self.yourCollectionView.scrollToItem(at: IndexPath(row: self.currentVisibleIndexPath, section: 0), at: .centeredHorizontally, animated: true)
}
On button second action,
if self.currentIndexPath == 1 {
self.currentIndexPath -= 1
self.yourCollectionView.scrollToItem(at: IndexPath(row: self.currentVisibleIndexPath, section: 0), at: .centeredHorizontally, animated: true)
} else {
return
}
And declare global variable
var currentIndexPath: Int = 0

Showing the last cell of Tableview

I want to show the bottom cells of my Tableview once the data are load I dont want to scroll every time to the bottom of Tableview to see the result
I have try this function in viewDidLoad but it did not work the page load at the top cells
if myArray.count > 0 {
tableView.scrollToRow(at: IndexPath(item:myArray.count - 1 , section: 0), at: .bottom, animated: false)
}
try use this
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [tableView] in
if myArray.count > 0 {
tableView.scrollToRow(at: IndexPath(item:myArray.count - 1 , section: 0), at: .bottom, animated: false)
}
}
You can leverage the usage of the scrollView's setContentOffset:animated: method
This answer should help you out: How to show last add cell on UITableview whileloading view or reloading tableview?

UICollectionView scrollToItem Not Working in iOS 14

I'm using a collection view, UICollectionView, and it works absolutely great...except that I cannot scroll to any particular item. It seems to always scroll to the "middle" is my best guess. In any case, whatever I send to scrollToItem seems to have no effect on the scrolling. I've put it in various locations throughout my view controller but with no success.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let lastIndex = IndexPath(row: messages.count-1, section: 0)
self.messagesView.scrollToItem(at: lastIndex, at: .bottom, animated: true)
}
UICollection View has bug in iOS 14 with scrollToItem. In iOS 14 it will only work if collection view paging will be desable.
So i got a solution if we have to scroll with both manual and programatically.
Specially for iOS 14 and above
self.collView.isPagingEnabled = false
self.collView.scrollToItem(at: IndexPath(item: scrollingIndex, section: 0), at: .left, animated: true)
self.collView.isPagingEnabled = true
You could try putting the scrolling code in viewDidLayoutSubviews which should get called after all table cells are loaded, which means your messages.count should work. Also, make sure you only have a single section in your collection view.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.scrollToBottom()
}
func scrollToBottom() {
DispatchQueue.main.async {
let lastIndex = IndexPath(item: messages.count-1, section: 0)
self.messagesView.scrollToItem(at: lastIndex, at: .bottom, animated: true)
}
}
Swift 5 / iOS 15
I was facing the same problem, so I tried this one-line code and get it done.
// here we slect the required cell instead of directly scrolling to the item and both work same.
self.collectionView.selectItem(at: IndexPath(row: self.index, section: 0), animated: true, scrollPosition: .left)
This is what worked for me, in case you want paging enabled:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.isPagingEnabled = false
collectionView.scrollToItem(
at: IndexPath(item: 1, section: 0),
at: .centeredVertically,
animated: false
)
collectionView.isPagingEnabled = true
}

UITableView add rows and scroll to bottom

I'm writing a table view where rows are added upon user interaction. The general behavior is simply to add a single row, then scroll to the end of the table.
This was working perfectly fine before iOS11, but now the scrolling always jumps from the top of the table instead of smoothly scrolling.
Here's the code that has to do with adding new rows:
func updateLastRow() {
DispatchQueue.main.async {
let lastIndexPath = IndexPath(row: self.currentSteps.count - 1, section: 0)
self.tableView.beginUpdates()
self.tableView.insertRows(at: [lastIndexPath], with: .none)
self.adjustInsets()
self.tableView.endUpdates()
self.tableView.scrollToRow(at: lastIndexPath,
at: UITableViewScrollPosition.none,
animated: true)
}
}
And
func adjustInsets() {
let tableHeight = self.tableView.frame.height + 20
let table40pcHeight = tableHeight / 100 * 40
let bottomInset = tableHeight - table40pcHeight - self.loadedCells.last!.frame.height
let topInset = table40pcHeight
self.tableView.contentInset = UIEdgeInsetsMake(topInset, 0, bottomInset, 0)
}
I'm confident the error lies within the fact that multiple UI updates are pushed at the same time (adding row and recalculating edge insets), and tried chaining these functions with separate CATransaction objects, but that completely messes up asynchronous completion blocks defined elsewhere in the code which update some of the cell's UI elements.
So any help would be appreciated :)
I managed to fix the issue by simply just calling self.tableView.layoutIfNeeded() before adjusting the insets:
func updateLastRow() {
DispatchQueue.main.async {
let lastIndexPath = IndexPath(row: self.currentSteps.count - 1, section: 0)
self.tableView.beginUpdates()
self.tableView.insertRows(at: [lastIndexPath], with: .none)
self.tableView.endUpdates()
self.tableView.layoutIfNeeded()
self.adjustInsets()
self.tableView.scrollToRow(at: lastIndexPath,
at: UITableViewScrollPosition.bottom,
animated: true)
}
}
Make sure that you are respecting the Safe Areas.
For more details, check: https://developer.apple.com/ios/update-apps-for-iphone-x/

How to execute code after an animation without using the completion block?

I would like to execute some code after a built-in animation is completed.
I have a UITableView with a lot of cells/rows. Sometimes, when I do some operations and then I need to scroll to the top of my tableView. For that I use :
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: UITableViewScrollPosition.Top, animated: true)
But I need to perform some code once the top is reached, such a simple select and deselect of the 1rst row.
I implemented func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) from the UIScrollViewDelegate. It works fine (more or less, sometimes the animation isn't really smooth and we don't see the select/deselect "animation") except when I am already at the top of the tableView, then scrollViewDidEndScrollingAnimation isn't called.
So is there a way to execute some code once scrollToRowAtIndexPath:atScrollPosition:animated has been called?
EDIT:
When I'm talking about doing some operations, I'm talking about moving a row using moveRowAtIndexPath:toIndexPath from UITableView.
So when scrolling is needed it's fine, both animations take about the same amount of time. But when no scroll is needed, then the execution of the code I want to do after the animation, starts at the same time than the animation
You can use this Swift code which I adapted from an old Objective-C answer to scroll the view.
// First, test whether the tableView needs to scroll to the new position
var originalOffset = tableView.contentOffset.y;
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: UITableViewScrollPosition.Top, animated: false)
var offset = tableView.contentOffset.y;
if (originalOffset == offset) {
// No animation is needed since its already there
doThingAfterAnimation();
} else {
// We know it will scroll to a new position
// Return to originalOffset. animated:NO is important
tableView.setContentOffset(CGPointMake(0, originalOffset), animated: false);
// Do the scroll with animation so `scrollViewDidEndScrollingAnimation:` will execute
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: UITableViewScrollPosition.Top, animated: true)
}
And then of course:
func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
doThingAfterAnimation();
}

Resources