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)
Related
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];
}];
I want my tableview scroll to the bottom when first time into the view, but sometimes won't scroll to the bottom, and sometimes will made my table disappear. (usually happened when first time or second time).
here's my scrolling code:
-(void)tableScrollToBottom{
[self.tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];
[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionBottom
animated:YES];
});
}
and I also have [self tableScrollToBottom] in viewDidAppear, cause if I only scroll the table in viewDidLoad the table always won't scroll to very bottom, and I don't know why, it's really confusing me.
And I tried to put [self.tableView layoutIfNeeded] in [self tableScrollToBottom], and remove the dispatch_async, it seems like the first time I opened the app and into the view still can't scroll to the very bottom, after the first time, it'll scroll to the bottom correctly, I don't know what's the difference between the first time open the app and into that view and after the first time.(my data get from server and it's from previous, and my tableview row height is dynamic).
here's my code in [self tableScrollToBottom] now:
-(void)tableScrollToBottom {
[self.tableView layoutIfNeeded];
NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
and here's where I called [self tableScrollToBottom] :
-(void)getDicData {
for (int i = 0; i < _csDictArr.count; i ++) {
[_chatPersonArray addObject:_csDictArr[i][#"Person"]];
[_chatDataArray addObject:_csDictArr[i][#"Message"]];
[_chatDateTimeArr addObject:_csDictArr[i][#"Time"]];
}
[self.tableView reloadData];
[self tableScrollToBottom];
}
and I put [self getDicData] in ViewDidLoad, and I also call the tableScrollToBottom in viewDidAppear
-(void)viewDidAppear:(BOOL)animated {
[self tableScrollToBottom];
}
You should use CATransaction to make sure tableView was reload completely before scroll it to bottom.
[CATransaction begin];
[CATransaction setCompletionBlock:^{
CGFloat yOffset = 0;
if (self.tableView.contentSize.height > self.tableView.bounds.size.height) {
yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height;
}
[self.tableView setContentOffset:CGPointMake(0, yOffset) animated:YES];
}];
[self.tableView reloadData];
[CATransaction commit];
I have a horizontal UICollectionView that implements UICollectionViewLayout and uses targetContentOffsetForProposedContentOffset for a custom fling/paging functionality. Each cell is nearly the width of the screen with the next cell showing slightly to the right. The functionality works great, but I want to be able to delete the cell at index 0 when the current cell is index 1. I'm currently able to calculate and do this just fine, but upon deleting the index 0 cell it slides to the next cell (old index 2, new 1 (after deleting 0)) because of the current content offset. I'm not sure how I can delete index 0 while maintaining the current layout.
Right now in my delegate I'm doing:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
float cellWidth = self.collectionView.bounds.size.width - 20;
float currentPage = self.collectionView.contentOffset.x / cellWidth;
if (currentPage == 1) {
[self remove:0];
}
}
-(void)remove:(int)i {
[self.collectionView performBatchUpdates:^{
[self.data removeObjectAtIndex:0];
NSIndexPath *indexPath =[NSIndexPath indexPathForRow:0 inSection:0];
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
} completion:^(BOOL finished) {
}];
}
So the calculation and deletion works fine, but upon deleting [0], the collection view is scrolled to the next cell..and I'm not sure how to stop it.
I've tried self.collectionview.contentOffset = CGMakePoint(0,0) after the deleting, but the transition is still noticeably buggy. Any ideas on how to approach this problem?
so after a ton of trial and error I was able to resolve the issue. It might be a little hackish but I just wrapped the batch with a uiview animation that has no duration. Basically it overrides the deletion animation
[UIView animateWithDuration:0 animations:^{
[self.collectionView performBatchUpdates:^{
[scrollView setContentOffset:CGPointMake(0, 0) animated:NO];
[self.data removeObjectAtIndex:0];
NSIndexPath *indexPath =[NSIndexPath indexPathForRow:0 inSection:0];
[self.collectionView deleteItemsAtIndexPaths:#[indexPath]];
[self.collectionView.collectionViewLayout invalidateLayout];
} completion:nil];
}];
I am trying to figure out a way to animate the deletion of a UICollectionViewCell similarly to springboard in iOS 7. The deleted cell should shrink into nothingness and the remaining cells should slide over to fill its place (the sliding is the important part). I have tried:
• Setting the frame of each cell to the frame of the previous cell, like so:
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
CGRect rect = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:i - 1 inSection:0]].frame;
[UIView animateWithDuration:0.2 animations:^{
cell.frame = rect;
}];
But this did not work, as the cells did not move at all.
• Both of the following:
[self.collectionView performBatchUpdates:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
} completion:nil];
and
[UIView animateWithDuration:0.2 animations:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
}];
But these make the cells crossfade to their required spots, not slide.
I also found this project, which uses the following:
for (int i = index+1; i<[items count]; i++) {
SEMenuItem *item = [items objectAtIndex:i];
[UIView animateWithDuration:0.2 animations:^{
if (i < index + remainingNumberOfItemsInPage) {
int intVal = item.frame.origin.x;
if (intVal %3== 0)
[item setFrame:CGRectMake(item.frame.origin.x+2*item.frame.size.width, item.frame.origin.y-item.frame.size.height-5, item.frame.size.width, item.frame.size.height)];
else
[item setFrame:CGRectMake(item.frame.origin.x-item.frame.size.width, item.frame.origin.y, item.frame.size.width, item.frame.size.height)];
}
[item updateTag:item.tag-1];
}];
}
However, it is not in a UICollectionView, so it does not help me.
Note: I am doing this on an iPad, just in case it matters to you.
Any ideas?
Subclass UICollectionViewFlowLayout. Implement:
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attr = [self layoutAttributesForItemAtIndexPath:indexPath]; // might need to replace self with super
attr.transform = CGAffineTransformMakeScale(0, 0);
return attr;
}
In addition to duci9y's answer, (using attr.transform = CGAffineTransformMakeScale(0,0);), the following makes the tiles slide. I can't believe I didn't think of this before.
[self.collectionView performBatchUpdates:^{
[self.collectionView deleteItemsAtIndexPaths:#[indexPath]];
[dataSource removeObjectAtIndex:indexPath.row];
} completion:nil];
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)