Scroll Issue in IOS 13 - ios

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

Related

Using an action to scroll CollectionView to the next cell

I have a horizontal collectionview that adds a new cell every time I tap the button. I am attempting to scroll the collectionview to the next cell once the cells are no longer visible each time I tap the button.
The code I have now is not working properly
Code:
#IBAction func random(_ sender: Any) {
resultsCollection.reloadData()
let collectionBounds = resultsCollection.bounds
let contentOffset = CGFloat(floor(resultsCollection.contentOffset.x - collectionBounds.size.width))
self.moveToFrame(contentOffset: contentOffset)
}
func moveToFrame(contentOffset : CGFloat) {
let frame: CGRect = CGRect(x : contentOffset ,y : resultsCollection.contentOffset.y ,width : resultsCollection.frame.width,height : resultsCollection.frame.height)
resultsCollection.scrollRectToVisible(frame, animated: true)
}
How can I fix this so that it scrolls correctly when I tap the button?
After reload your data, call this lines.
let lastItem = resultsCollection(resultsCollection, numberOfItemsInSection: 0) - 1
let indexPath = IndexPath(row: lastItem, section: 0)
resultsCollection.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
Use the following code to achieve your needs. For animation, you just need to pass value true/false in animated of scrollToRow function.
Hope this will help you!
To scroll top without animation
func scrollToTopWithoutAnimation() {
DispatchQueue.main.async {
if self.dataArray.count > 0 {
let indexPath = IndexPath(row: 0, section: 0)
collectionView.scrollToItem(at: indexPath, at: .top, animated: false)
}
}
}
To scroll top with animation
func scrollToTopWithAnimation() {
DispatchQueue.main.async {
if self.dataArray.count > 0 {
let indexPath = IndexPath(row: 0, section: 0)
collectionView.scrollToItem(at: indexPath, at: .top, animated: true)
}
}
}
Set IndexPath row as per your needs

setContentOffset is not scrolling table to last cell

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;

How to add last row to UITableView with smooth scrolling?

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];

How to get the last indexpath of a uicollectionview?

I want to get the last indexpath of a uicollectionview.
I tried this:
NSInteger section = [self numberOfSectionsInCollectionView:self.collectionView] - 1;
NSInteger item = [self collectionView:self.collectionView numberOfItemsInSection:section] - 1;
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:item inSection:section];
But couldn't find the last indexpath of an uicollectionview .
Any suggestions,
Thanks in advance.
NSInteger lastSectionIndex = [tableView numberOfSections] - 1;
NSInteger lastRowIndex = [tableView numberOfRowsInSection:lastSectionIndex] - 1;
// Now just construct the index path
NSIndexPath *pathToLastRow = [NSIndexPath indexPathForRow:lastRowIndex inSection:lastSectionIndex];
Answer for Swift 3
extension UICollectionView {
func scrollToLastIndexPath(position:UICollectionViewScrollPosition, animated: Bool) {
self.layoutIfNeeded()
for sectionIndex in (0..<self.numberOfSections).reversed() {
if self.numberOfItems(inSection: sectionIndex) > 0 {
self.scrollToItem(at: IndexPath.init(item: self.numberOfItems(inSection: sectionIndex)-1, section: sectionIndex),
at: position,
animated: animated)
break
}
}
}
}
Note that my experience shows that animated property should be false for initializing the collectionView.
From the code above, as an answer to your question use this method:
func lastIndexPath() -> IndexPath? {
for sectionIndex in (0..<self.numberOfSections).reversed() {
if self.numberOfItems(inSection: sectionIndex) > 0 {
return IndexPath.init(item: self.numberOfItems(inSection: sectionIndex)-1, section: sectionIndex)
}
}
return nil
}

UICollectionView scroll to last item without Animation

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

Resources