How to focus on last cell in a UITableview with scrolling animation? - ios

I have a UITableview in my xcode project.In that Comments are listed.How can i focus on a last cell of my TableView with scrolling animation?

Below method will find the last index of you table view and will focus to that cell with an animation
-(void)goToBottom
{
NSIndexPath *lastIndexPath = [self lastIndexPath];
[<YOUR TABLE VIEW> scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
Code to find the last index of your tableview.
-(NSIndexPath *)lastIndexPath
{
NSInteger lastSectionIndex = MAX(0, [<YOUR TABLE VIEW> numberOfSections] - 1);
NSInteger lastRowIndex = MAX(0, [<YOUR TABLE VIEW> numberOfRowsInSection:lastSectionIndex] - 1);
return [NSIndexPath indexPathForRow:lastRowIndex inSection:lastSectionIndex];
}
Add this code somewhere in your view controller
[self performSelector:#selector(goToBottom) withObject:nil afterDelay:1.0];

Keep in mind that a UITableView inherits from UIScrollView
-(void)scrollToBottom:(id)sender
{
CGSize r = self.tableView.contentSize;
[self.tableView scrollRectToVisible:CGRectMake(0, r.height-10, r.width, 10) animated:YES];
}
execute it like
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(…);
[button addTarget:self action:#selector(scrollToBottom:) forControlEvents:UIControlEventTouchUpInside];
(You dont need to use performSelector:…)
Another solution would be selecting the last row. The table view will take care of the rest.
-(void)scrollToBottom:(id)sender
{
NSInteger lasSection = [self.tableView numberOfSections] - 1;
NSInteger lastRow = [self.tableView numberOfRowsInSection:lasSection]-1;
//this while loops searches for the last section that has more than 0 rows.
// maybe you dont need this check
while (lastRow < 0 && lasSection > 0) {
--lasSection;
lastRow = [self.tableView numberOfRowsInSection:lasSection]-1;
}
//if there is no section with any row. if your data source is sane,
// this is not needed.
if (lasSection < 0 && lastRow < 0)
return;
NSIndexPath *lastRowIndexPath =[NSIndexPath indexPathForRow:lastRow inSection:lasSection];
[self.tableView selectRowAtIndexPath:lastRowIndexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];
}
the button is the same.

Swift 3:
let indexPath = IndexPath(row: /*ROW TO SCROLL TO*/, section: /*SECTION TO SCROLL TO*/)
tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)

Related

Adding a custom nested UIView below UITableViewCell

I am adding a expandale/collapsable UIView below UITableviewCell. I am able to add and hide the view on button action but unable to remove it when view is shown in another cell. I am updating the view height constraint to 0 on hiding.
Here's my code:
-(void)doneButtonClicked:(UIButton*)sender{
CGPoint buttonPosition = [sender convertPoint:CGPointZero
toView:_tableView];
NSIndexPath *indexPath = [_tableView
indexPathForRowAtPoint:buttonPosition];
CustomCell *cell = [_tableView cellForRowAtIndexPath:indexPath];
for (int i = 0; i < [_expandedIndexPaths count]; i++){
if (i != indexPath.row && [_expandedIndexPaths count] > 0){
[self.expandedIndexPaths removeObject:indexPath];
//Animation effect for expanding/collapsing view
[cell animateClosed];
}
}
if (sender.tag == 0) {
[self.expandedIndexPaths addObject:indexPath];
[cell animateOpen];
//[self.expandedIndexPaths removeObject:indexPath];
}else{
[self.expandedIndexPaths removeObject:indexPath];
[cell animateClosed];
}
[_tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
The constraint update should also happen in the cellForRow method. Set the constant to 0 there.
If you want the view to appear again for the cell that open it in first
place and didn't close it. Add hidden state boolean var in the cell
class and update every time you update that constant. Then on
cellForRow check the value of that boolean and set the constant
accordingly.

Insert UITableView row without animation

I'm using insertRowsAtIndexPaths:withRowAnimation, and setting with UITableViewRowAnimationNone. Then i use the the mehod below
Insert UITableView row without ANY animation
But it causes a flash when reload the tableview. How can i fix the flash?
Thank you。
My demo is want to pull to load more local data and show on the tableview,but the last position is previous contentOffset.So i overwrite the UITableView's setContentSize function.
- (void)setContentSize:(CGSize)contentSize {
if (!CGSizeEqualToSize(self.contentSize, CGSizeZero)) {
if (contentSize.height> self.contentSize.height) {
CGPoint offset = self.contentOffset;
offset.y += contentSize.height - self.contentSize.height;
self.contentOffset = offset;
}
}
[super setContentSize:contentSize];
}
When pull to refresh, load the data and refresh tableview:
for (long j = 0; j < result.count; j++) {
NSIndexPath *index = [NSIndexPath indexPathForRow:j inSection:0];
[array addObject:index];
}
[UIView performWithoutAnimation:^{
[_mainTableView beginUpdates];
[_mainTableView insertRowsAtIndexPaths:array withRowAnimation:UITableViewRowAnimationNone];
[_mainTableView endUpdates];
}];

Add cell at bottom and scroll UITableView

I want to add cells at the bottom of a UITableView, and scroll to make it fully visible (just like in Whatsapp when you send or receive a message).
I've tried doing this:
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:[self.tableView numberOfRowsInSection:0]-1 inSection:0] atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
But this makes a graphical glitch and the table flashes and make a bad scrolling instead of a smooth one.
Any help with this?
here what I have done is, inserted a row and then changed the content inset of the tableView
-(IBOutlet)sendButton:(id)sender
{
[array addObject:textView.text];
[self addAnotherRow];
}
- (void)addAnotherRow {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:(array.count - 1) inSection:0];
[self.messagesTableView beginUpdates];
[self.messagesTableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.messagesTableView endUpdates];
[self updateContentInsetForTableView:self.messagesTableView animated:YES];
// TODO: if the scroll offset was at the bottom it can be scrolled down (allow user to scroll up and not override them)
[self.messagesTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
- (void)updateContentInsetForTableView:(UITableView *)tableView animated:(BOOL)animated {
NSUInteger lastRow = [self tableView:tableView numberOfRowsInSection:0];
NSLog(#"last row: %lu", (unsigned long)lastRow);
NSLog(#"items count: %lu", (unsigned long)array.count);
NSUInteger lastIndex = lastRow > 0 ? lastRow - 1 : 0;
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:lastIndex inSection:0];
CGRect lastCellFrame = [self.messagesTableView rectForRowAtIndexPath:lastIndexPath];
// top inset = table view height - top position of last cell - last cell height
CGFloat topInset = MAX(CGRectGetHeight(self.messagesTableView.frame) - lastCellFrame.origin.y - CGRectGetHeight(lastCellFrame), 0);
// What about this way? (Did not work when tested)
// CGFloat topInset = MAX(CGRectGetHeight(self.tableView.frame) - self.tableView.contentSize.height, 0);
NSLog(#"top inset: %f", topInset);
UIEdgeInsets contentInset = tableView.contentInset;
contentInset.top = topInset;
NSLog(#"inset: %f, %f : %f, %f", contentInset.top, contentInset.bottom, contentInset.left, contentInset.right);
NSLog(#"table height: %f", CGRectGetHeight(self.messagesTableView.frame));
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState;
[UIView animateWithDuration:animated ? 0.25 : 0.0 delay:0.0 options:options animations:^{
tableView.contentInset = contentInset;
} completion:^(BOOL finished) {
}];
}
Note: to view existing messages from array, you have to include this
also.
- (void)viewDidLayoutSubviews {
[self updateContentInsetForTableView:self.messagesTableView animated:NO];
}
example function to add an item:
- (IBAction)addItem:(UIBarButtonItem *)sender {
[self.items addObject:[NSString stringWithFormat:#"Item %lu", self.items.count + 1]];
NSIndexPath *indexPathToInsert = [NSIndexPath indexPathForRow:self.items.count - 1 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPathToInsert] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView scrollToRowAtIndexPath:indexPathToInsert atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
for me the animation looks perfectly fine this way!
Well, what I did is use [self.tableView scrollToRowAtIndexPath... but calling it from a 0.1s NSTimer.
Make insertions animated and after endUpdates scroll bottom not animating. Worked for me
Swift
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths(newIndexPaths, withRowAnimation: UITableViewRowAnimation.Top)
self.tableView.endUpdates()
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: self.tableDataCount-1, inSection: 0), atScrollPosition: .Bottom, animated: false)

Keeping UITableView and UICollectionView scroll position in sync

I have a UIViewController that is able to show its content via a UICollectionView (cv) or via aUITableView (tv).
Both views are added to the view hierarchy via storyboard and I'm just hiding the one that should not be shown. Both the UICollectionView and the UITableView have the same data source.
The UICollectionView is showing the content in a two items per row grid, the UITableView obviously in a one item per row grid.
So what I want to happen is if the user decides to switch between the views the newly shown view should scroll to the element that was visible in the other view.
I did some experimenting with scrollToRowAtIndexPath:atScrollPosition:animated: and scrollToItemAtIndexPath:atScrollPosition:animated: with no success.
I understand, that both the UICollectionView and UITableView are able to return their current visible items'/rows' indexPaths, but how can i transform those indexPath to a indexPath of the other view and tell them to scroll to that element?
My current touch event handling of the switch button looks like this:
- (IBAction)switchView:(id)sender {
self.showListView = !self.showListView;
UIView *fromView, *toView;
NSIndexPath *indexPath;
if (self.showListView)
{
fromView = self.ArticleCollection;
toView = self.ArticleList;
CGRect visibleRect = (CGRect){.origin = self.ArticleCollection.contentOffset, .size = self.ArticleCollection.bounds.size};
CGPoint visiblePoint = CGPointMake(100, CGRectGetMidY(visibleRect));
indexPath = [self.ArticleCollection indexPathForItemAtPoint:visiblePoint];
}
else
{
fromView = self.ArticleList;
toView = self.ArticleCollection;
CGRect visibleRect = (CGRect){.origin = self.ArticleList.contentOffset, .size = self.ArticleList.bounds.size};
CGPoint visiblePoint = CGPointMake(100, CGRectGetMidY(visibleRect));
indexPath = [self.ArticleList indexPathForRowAtPoint:visiblePoint];
}
NSInteger row = indexPath.row;
if (self.showListView) {
[self.ArticleList scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:row*2 inSection:0]
atScrollPosition:UITableViewScrollPositionTop
animated:NO];
} else {
[self.ArticleCollection scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:ceil(row/2)
inSection:0
atScrollPosition:UICollectionViewScrollPositionCenteredVertically
animated:NO]];
}
// Here a transitionFromView:ToView:duration:options:completion call follows
}
Thanks in advance for any contribution to my problem.
Appart from a little typo inside the scrollToItemAtIndexPath:atScrollPosition:animated: call:
[self.ArticleCollection scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:row
inSection:0]
atScrollPosition:UICollectionViewScrollPositionCenteredVertically
animated:NO];
I did think indexPath.row would hold different values whether it came from the cv's visible indexPath or the tv's visible indexPath so I calculated the new row by multiplying or dividing through 2. But as it turns out the rows are identical in both cases, so I just needed to use the same value for both cases:
NSInteger row = indexPath.row;
if (self.showListView) {
[self.ArticleList scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:0]
atScrollPosition:UITableViewScrollPositionTop
animated:NO];
} else {
[self.ArticleCollection scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:row
inSection:0]
atScrollPosition:UICollectionViewScrollPositionCenteredVertically
animated:NO];
}
Sorry for the inconvenience.

Maintaining scroll position after reload in tableView

I've got a tableView. When user taps one of records, I check it's property and basing on this I'm filtering results and showing only similar results - so I need to refresh the tableView.
The problem is that user can scroll up/down the tableView. I need to scroll the tableView so the cell is exactly at the same UITableViewScrollPosition as before the refresh.
Obviously, I'm saving last tapped item
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
_lastSelectedItem = [self itemForIndexPath:indexPath];
}
Then after reloading the tableView:
NSIndexPath *indexPath = [self indexPathForItem:_lastSelectedItem];
if (_tableView.numberOfSections > indexPath.section && [_tableView numberOfRowsInSection:indexPath.section] > indexPath.row) {
[_tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
_lastSelectedItem = nil;
}
It would be good but... user could not finish at UITableViewScrollPositionMiddle. He could have finish scrolling at UITableViewScrollPositionTop or UITableViewScrollPositionBottom or even somewhere between.
-- edit --
Calculating via offset is also problematic, as the offset is the difference between start of view and top table scroll position.. while I'm not interested in this value :/.
Because my structure was quite complicated I finished to do it this way (eg. expandable sections etc). Please do mind, that I'm saving _lastSelectedItem, which is my object at datasource, as indexPath will change after refresh.
- (void)refresh {
[self saveLastScrollPosition];
[self reloadData]; // inside we reload data + reload tableView
[self scrollToLastScrollPosition];
}
- (NSInteger)heightToMinYOfCellAtIndexPath:(NSIndexPath *)indexPath {
NSInteger sections = indexPath.section;
NSInteger totalRows = 0;
for (NSInteger section = 0; section < indexPath.section; section++) {
totalRows += [self.tableView numberOfRowsInSection:section];
}
totalRows += indexPath.row;
return ((sections + 1) * kHeaderHeight + totalRows * kRowHeight);
}
- (void)saveLastScrollPosition {
if (_lastSelectedItem) { // make sure we have that item selected
NSIndexPath *indexPath = [self itemForItem:_lastSelectedItem];
NSInteger aboveItemHeight = [self heightToMinYOfCellAtIndexPath:indexPath];
CGFloat contentOffsetY = self.tableView.contentOffset.y;
_previousOffset = aboveItemHeight - contentOffsetY;
}
}
- (void)scrollToLastScrollPostion {
if (_lastSelectedItem) { // make sure we have that item selected
NSIndexPath *indexPath = [self itemForItem:_lastSelectedItem];
if (self.tableView.numberOfSections > indexPath.section && [self.tableView numberOfRowsInSection:indexPath.section] > indexPath.row) { // make sure the indexPath still exist after reload
NSInteger aboveItemHeight = [self heightToMinYOfCellAtIndexPath:indexPath]; // height of items above selected index
CGPoint offset = CGPointMake(0.f, aboveItemHeight - _previousOffset);
// in case index should be higher: eg earlier item was at index (4,8) now is at (0,0)
if (offset.y < 0) {
offset.y = 0;
}
[self.tableView setContentOffset:offset animated:NO]; // just jump, user shouldn't see this
_previousOffset = 0; // forget the calculated values
_lastSelectedItem = nil;
}
}
}

Resources