I'm building chat interface using UITableView. i'm adding cells using below code
self.tableView.beginUpdates()
self.tableView.insertRows(at: [IndexPath(row: self.messageCards.count - 1, section: 0)], with: .bottom)
self.tableView.endUpdates()
and after adding i'm using scrollToRowAtIndexPath to show last inserted row.
let indexPath = IndexPath(row: self.messageCards.count - 1, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: false)
but at the end both animation are mixing and scrolling not happening smoothly.
You can try this, I have tested the code:
arrayData.addObject(str)
let indexpath = NSIndexPath(forRow: arrayData.count-1, inSection: 0)
tblView.insertRowsAtIndexPaths([indexpath], withRowAnimation: UITableViewRowAnimation.Automatic)
tblView.scrollToRowAtIndexPath(indexpath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: true)
Following is output: let me know if you want complete source.
Please check out this.
[UITableView animateWithDuration:5.0 delay:0.1 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:self.messageCards.count-1 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
} completion:^(BOOL finished) {
NSLog(#"animated");
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathWithIndex:self.messageCards.count-1] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}];
Its wonder, but to achieve smooth adding when scrolling just need to call reloadData()
func fetchNext(_ countToFetch: Int? = 10) {
let existingCount = items?.count ?? 0
networkService?.items(bySkip: existingCount, take: countToFetch, completion: { (data, error) in
if let newItems = data?.items() as? [Element], newItems.count > 0 {
self.items?.append(contentsOf: newItems)
self.refreshCallback?()
}
})
}
Callback is:
datasource?.refreshCallback = { [weak self] in
if #available(iOS 10.0, *) {
self?.table.refreshControl?.endRefreshing()
}
self?.table.reloadData()
}
[self.messageCards addObject:#"DATA Whatever you want"];
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.messageCards.count-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
Related
In order to achieve scroll To bottom for a table view, I am using the below code.
extension UITableView {
func scrollToBottom(){
let indexPath = IndexPath(
row: self.numberOfRows(inSection: self.numberOfSections -1) - 1,
section: self.numberOfSections - 1)
self.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
This is working perfectly fine for all the devices whose versions are below 13, but in ios 13 it is not scrolling completely to last cell , it is stopping in between the last cell (approximate 40 pixel from bottom).
I also tried alternate ways by
setting content Offset
setting the scroll to visible rect all
having a delay for 1.0 seconds
but all of these having the same behaviour, not scrolling completely.
If you're facing this issue because of having different cells with different heights then below code will probably work for you:
private func moveTableViewToBottom(indexPath: IndexPath) {
tableView.scrollToRow(at: indexPath, at: .bottom, animated: false)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: false)
}
}
Try this
func scrollToBottom(){
DispatchQueue.main.async {
let indexPath = IndexPath(row: self.yourDataSourceArray-1, section: self.numberOfSections - 1)
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
Thank Shivam Pokhriyal
It helps me work properly on iOS 13, But I don't know exactly why
Swift:
private func moveTableViewToBottom(indexPath: IndexPath) {
tableView.scrollToRow(at: indexPath, at: .bottom, animated: false)
if #available(iOS 13.0, *) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: false)
}
}
}
OC:
- (void)scrollToBottomAnimated:(BOOL)animated {
NSInteger rows = [self.tableView numberOfRowsInSection:0];
if (rows > 0) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rows-1 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:animated];
if (#available(iOS 13.0, *)) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSInteger rows = [self.tableView numberOfRowsInSection:0];
if (rows > 0) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:rows-1 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:animated];
}
});
} else {
// Fallback on earlier versions
}
}
}
I am trying to scroll the table to last row using contentOffset method.This method works fine when data is not much but if user has scrolled to much it does take table to last cell. I am using the following code
self.delegate.table.setContentOffset(CGPointMake(0, table.contentSize.height - table.frame.size.height), animated: false)
I think the preferred way would be calling
func scrollToRow(at indexPath: IndexPath,
at scrollPosition: UITableViewScrollPosition,
animated: Bool)
at the UITableView, see here
Why not use the scrollToRow method.
let lastIndex:NSIndexPath = NSIndexPath(forRow: tableView.numberOfRowsInSection(0) - 1, inSection: 0)
tableView.scrollToRowAtIndexPath(lastIndex, atScrollPosition: .Middle, animated: true)
The time this takes to scroll is based on the contentSize. Strangely this time is still used when animated is set to false. To make it more snappy set your own duration in an animation block.
UIView.animateWithDuration(aSmallValue) {
self.tableView.scrollToRowAtIndexPath(lastIndex, atScrollPosition: .Middle, animated: true)
}
Try this it works for me,
code for objective-c
NSIndexPath *scrollIndexPath = [NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0];
[[self tableView] scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
Code for Swift 3.0
let scrollIndexPath:IndexPath = IndexPath(row: (tblView.numberOfRows(inSection: 0)-1), section: 0)
tblView.scrollToRow(at: scrollIndexPath, at: .bottom, animated: false)
let offsetOfTable = CGPoint(x:0, y:self.yourTableView.contentSize.height - self.yourTableView.frame.size.height + self.FOOTER_HEIGHT);
self.yourTableView.contentOffset = offsetOfTable;
I'm trying to only display items that meet the criteria ( if location <= searchedDistance) but it seems like this is a very inefficient way to display this data. Does this function reload the entire tableview after each location is checked? If so, isn't this a horrible use of memory/time?
What's a better alternative?
var locations = [Int: Int]()
func searchSuccessful() {
var row: Int = 0
var section: Int = 0
for (index, location) in locations {
if location <= searchedDistance {
self.theTableView.beginUpdates()
let indexPath = NSIndexPath(forRow: row, inSection: section)
self.theTableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
self.theTableView.endUpdates()
}
}
}
Put the begin/end update outside of the loop.
self.theTableView.beginUpdates()
// Do all the changes in a single place, surrounded by begin/end updates
for (index, location) in locations {
if location <= searchedDistance {
let indexPath = NSIndexPath(forRow: row, inSection: section)
self.theTableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
self.theTableView.endUpdates()
Swift
self.tableView.beginUpdates()
let indexPath = NSIndexPath(forRow: row, inSection: section)
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
self.tableView.endUpdates()
Obj-C
[self.tableView beginUpdates];
NSIndexPath * indexPath = [NSIndexPath indexPathForRow:row inSection:section];
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
I have a UICollectionView to display chat messages. At the beginning I load the existing messages to the collectionView and scroll the collectionView down to the last message with this method:
- (void)scrollToLastMessageAnimated:(BOOL)animated;
{
if (_messages.count == 0) { return; }
NSUInteger indexOfLastSection = _messagesBySections.count - 1;
NSInteger indexOfMessageInLastSection = [_messagesBySections[indexOfLastSection] count] - 1;
NSIndexPath *path = [NSIndexPath indexPathForItem:indexOfMessageInLastSection
inSection:indexOfLastSection];
[_collectionView scrollToItemAtIndexPath:path
atScrollPosition:UICollectionViewScrollPositionCenteredVertically
animated:animated];
}
This only works animated when I call it in the viewDidAppear: method and not in viewWillAppear: method. How can I scroll down without animation?
Here it is...
NSInteger section = [self.collectionView numberOfSections] - 1;
NSInteger item = [self.collectionView numberOfItemsInSection:section] - 1;
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:(UICollectionViewScrollPositionBottom) animated:YES];
I also provided my answer in another question of yours but I am also writing it here, since this answer is being related only with this problem
Solution
To scroll the view at the last index without crashing before view appears, you first need to trigger the reload of your collectionView data. After it has been reloaded call your method to scroll your view.
-(void)viewWillAppear:(BOOL)animated {
[collectionView reloadData];
[self scrollToLastMessageAnimated:YES];
}
Update
[collectionView setContentOffset:CGPointMake(0, CGFLOAT_MAX)]
Swift 3.0 Code :
let index = IndexPath(item: (self.mFetchedResultsControllerVar?.fetchedObjects?.count)! - 1, section: 0)
self.collectionView?.scrollToItem(at: index, at: UICollectionViewScrollPosition.bottom, animated: true)
A more foolproof solution that handles multiple/0 sections/items
extension UICollectionView {
func scrollToLastItem(at scrollPosition: UICollectionViewScrollPosition = .centeredHorizontally, animated: Bool = true) {
let lastSection = numberOfSections - 1
guard lastSection >= 0 else { return }
let lastItem = numberOfItems(inSection: lastSection) - 1
guard lastItem >= 0 else { return }
let lastItemIndexPath = IndexPath(item: lastItem, section: lastSection)
scrollToItem(at: lastItemIndexPath, at: scrollPosition, animated: animated)
}
}
This Extension scrolls to the last section that actually has items.
Best approach so far.
public extension UICollectionView {
func scrollToLastItem() {
scrollToLastItem(animated: true, atScrollPosition: .bottom)
}
func scrollToLastItem(animated: Bool, atScrollPosition scrollPosition: UICollectionViewScrollPosition) {
guard numberOfSections > 0 else {
return
}
var sectionWithItems: SectionInfo?
for section in Array(0...(numberOfSections - 1)) {
let itemCount = numberOfItems(inSection: section)
if itemCount > 0 {
sectionWithItems = SectionInfo(numberOfItems: itemCount, sectionIndex: section)
}
}
guard let lastSectionWithItems = sectionWithItems else {
return
}
let lastItemIndexPath = IndexPath(row: lastSectionWithItems.numberOfItems - 1, section: lastSectionWithItems.sectionIndex)
scrollToItem(at: lastItemIndexPath, at: scrollPosition, animated: animated)
}
}
The problem was actually caused by problematic Autolayout constraints,
see my other thread for the solution that helped me in the end: https://stackoverflow.com/a/24033650/2302437
Can any one help me out with UITableView animating issue?
By default we have animation for deleting cell and reordering cells in UITableView.
Can we have animated adding cell, if so how to do it.
I have checked out Three20, did not not get how twitter has done the table expand animation under MyProfile>ReTweets.
Want to try it without Three20 frameowrk, using the existing animation in UITableView.
You can use the following UITableView method:
- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
Example with self.dataSource being a mutable array and your table only having 1 section:
[self.dataSource addObject:#"New Item"];
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:[self.dataSource count]-1 inSection:0];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
Note: Subtracted 1 from datasource count since NSIndexPath is zero indexed.
Swift
Example array that is the data source for the table view
var myArray = ["Cow", "Camel", "Sheep", "Goat"]
The following will add a row with animation at the top of the table view.
// add item to current array
myArray.insert("Horse", atIndex: 0)
// insert row in table
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
Multiple updates
If you need to do multiple insertions and/or deletions then surround them with beginUpdates() and endUpdates().
tableView.beginUpdates()
tableView.insertRowsAtIndexPaths([addIndexPath1, addIndexPath2, ...], withRowAnimation: .Fade)
tableView.deleteRowsAtIndexPaths([deleteIndexPath1, deleteIndexPath2, ...], withRowAnimation: .Fade)
tableView.endUpdates()
Further reading
Documentation
Use reloadRowsAtIndexPaths:indexPath
tableView.beginUpdates()
[tableView reloadRowsAtIndexPaths:indexPath withRowAnimation:UITableViewRowAnimationAutomatic];
tableView.endUpdates()
i hope this will help you..
In swift is also possible using:
func insertRow(entries: [String]) {
tableView.performBatchUpdates({
self.tableView.insertRows(at: [IndexPath(row: entries.count - 1, section: 0)], with: .bottom)
}, completion: nil)
}