Find UITableViewScrollPosition to properly scroll to a UITableViewCell - ios

I am trying to scroll to a particular UITableViewCell and it's works but sometimes if row is at the bottom of UITableView then it fails to scroll at that particular row. What I want to know is how I can identify which scroll position should be passed into the below method ?
- (void)scrollToObject:(AdvisoriesModel*)output {
NSInteger index = -1;
for (AdvisoriesModel* memberSites in self.AMAList) {
if ([memberSites.amaId isEqualToString:output.amaId]) {
index = [self.AMAList indexOfObject:memberSites];
break;
}
}
if (index != -1) {
#try {
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.tableAMA scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNone animated:YES];
[self.tableAMA selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Put your code here
[self.tableAMA deselectRowAtIndexPath:indexPath animated:YES];
});
} #catch (NSException *exception) {
NSLog(#"Exception occured while scrolling to Custom Fly Site %#",[exception description]);
}
}
}

Related

iOS:set scrollPosition to `UITableViewScrollPositionMiddle` it did not scroll to correct position

This is my code below:
if (![self.cityLabel.text isEqualToString:#""] && dataOfCity.count != 0) {
for (int i = 0; i < dataOfCity.count; i ++) {
if ([dataOfCity[i] isEqualToString:self.cityLabel.text]) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[_tab_city selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionMiddle];
}
}
}
And the result is the picture which is not in the middle of the tableView below :
I think I understand what happens:
the table view is already performing another animation and the scrollToRowAtIndexPath is ignored.
Wrapping the method in CATransaction block works for me (swift code):
CATransaction.begin()
tableView.beginUpdates()
tableView.scrollToRowAtIndexPath(cell.indexPath, atScrollPosition: .Middle, animated: true)
tableView.endUpdates()
CATransaction.commit()
Another solution that worked for me was using performSelectorWithDelay, with the delay being long enough for other animations on the table view to finish.
Answer:
I have found my question's reason:
I do that code before uiview animation, so it will show that wrong place,
I replace those code to the completion block,so the issue resolved:
- (void)show{
if (self.isOpenState == YES ) {
return;
}
[UIView animateWithDuration:0.3 animations:^{
//CGRect frame = self.backOfTabAndTrailer.frame;
self.opacity_back.alpha = 0.2;
//frame.size.height = _rowHeight * _numberOfRow;
//[self.backOfTabAndTrailer setFrame:frame];
self.backOfTabAndTrailer.frame = CGRectMake(_backOfTabAndTrailer.frame.origin.x, _backOfTabAndTrailer.frame.origin.y, _backOfTabAndTrailer.frame.size.width, _rowHeight * _numberOfRow);
self.tab_province.frame = CGRectMake(self.tab_province.frame.origin.x, self.tab_province.frame.origin.y, self.tab_province.frame.size.width, _rowHeight * _numberOfRow);
self.tab_city.frame = CGRectMake(self.tab_city.frame.origin.x, self.tab_city.frame.origin.y, self.tab_city.frame.size.width, _rowHeight * _numberOfRow);
self.tab_country.frame = CGRectMake(self.tab_country.frame.origin.x, self.tab_country.frame.origin.y, self.tab_country.frame.size.width, _rowHeight * _numberOfRow);
self.trailerView.frame = CGRectMake(_backOfTabAndTrailer.frame.origin.x, _rowHeight * _numberOfRow, self.trailerView.frame.size.width, Height_Of_Trailer);
self.trailerImage.frame = CGRectMake(_allFrame.size.width / 2 - Height_Of_Trailer / 2, 0, Height_Of_Trailer, Height_Of_Trailer);
self.trail_line.frame = CGRectMake(self.backOfTabAndTrailer.frame.origin.x, 0, self.trail_line.frame.size.width, 1);
self.trailerButton.frame = CGRectMake(self.backOfTabAndTrailer.frame.origin.x, 0, self.trailerButton.frame.size.width, 1);
} completion:^(BOOL finished){
self.isOpenState = YES;
/* 让tabview滚到被选中的地方 */
if (![self.provinceLabel.text isEqualToString:#""] && dataOfProvince.count != 0) {
for (int i = 0; i < dataOfProvince.count; i ++) {
if ([dataOfProvince[i] isEqualToString:self.provinceLabel.text]) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[_tab_province selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionMiddle];
}
}
}
if (![self.cityLabel.text isEqualToString:#""] && dataOfCity.count != 0) {
for (int i = 0; i < dataOfCity.count; i ++) {
if ([dataOfCity[i] isEqualToString:self.cityLabel.text]) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[_tab_city selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionMiddle];
}
}
}
if (![self.countryLabel.text isEqualToString:#""] && dataOfCountry.count != 0) {
for (int i = 0; i < dataOfCountry.count; i ++) {
if ([dataOfCountry[i] isEqualToString:self.countryLabel.text]) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[_tab_country selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionMiddle];
}
}
}
}];
}

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)

Add rows to the beginning of a tableview without animation

I want to add rows to a tableview with begin/endUpdates to prevent the jump of the tableview when i do reloadData
this is my code
- (void)updateTableWithNewRowCount:(NSInteger)rowCount
andNewData:(NSArray *)newData {
// Save the tableview content offset
CGPoint tableViewOffset = [self.messagesTableView contentOffset];
// Turn of animations for the update block
// to get the effect of adding rows on top of TableView
[UIView setAnimationsEnabled:NO];
[self.messagesTableView beginUpdates];
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[rowsInsertIndexPath addObject:tempIndexPath];
// [self.messages insertObject:[newData objectAtIndex:i] atIndex:i];
[self addMessage:[newData objectAtIndex:i]];
heightForNewRows =
heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
[self.messagesTableView insertRowsAtIndexPaths:rowsInsertIndexPath
withRowAnimation:UITableViewRowAnimationNone];
tableViewOffset.y += heightForNewRows;
[self.messagesTableView endUpdates];
[UIView setAnimationsEnabled:YES];
[self.messagesTableView setContentOffset:tableViewOffset animated:NO];
}
And sometimes (not everytime) I get this error
invalid number of rows in section 0.
The number of rows contained in an existing section after
the update (2) must be equal to the number of rows contained in that section
before the update (2), plus or minus the number of rows inserted or deleted
from that section (2 inserted, 0 deleted) and plus or minus the number of
rows moved into or out of that section (0 moved in, 0 moved out).
How do i prevent this error ?
if you dont want animation then use directly
- (void)updateTableWithNewRowCount:(NSInteger)rowCount
andNewData:(NSArray *)newData {
// Save the tableview content offset
CGPoint tableViewOffset = [self.messagesTableView contentOffset];
// Turn of animations for the update block
// to get the effect of adding rows on top of TableView
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[rowsInsertIndexPath addObject:tempIndexPath];
// [self.messages insertObject:[newData objectAtIndex:i] atIndex:i];
[self addMessage:[newData objectAtIndex:i]];
heightForNewRows =
heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
tableViewOffset.y += heightForNewRows;
[self.messagesTableView reloadData];
[self.messagesTableView setContentOffset:tableViewOffset animated:NO];
}
If you want to quickly jump to a selected indexPath (with or without animation), you can use this function:
//My function to populate
//tableView is synthesized
-(void)setNewMessage:(NSString*)message{
// ...
NSIndexPath *selectedIndexPath = [tableView indexPathForSelectedRow];
// if your selectedIndexPath==nil, the table scroll stay in the same position
[self reloadData:selectedIndexPath animated:NO];
}
//My reload data wrapper
-(void)reloadData:(NSIndexPath *)selectedIndexPath animated:(BOOL)animated{
[tableView reloadData];
// atScrollPosition can receive different parameters (eg:UITableViewScrollPositionMiddle)
[tableView scrollToRowAtIndexPath:selectedIndexPath
atScrollPosition:UITableViewScrollPositionTop
animated:animated];
}
Once I needed that same flexibility in a tableView and this function has met my needs. You don`t need the "setAnimationsEnabled", just use:
[tableView scrollToRowAtIndexPath:nil
atScrollPosition:UITableViewScrollPositionNone
animated:NO];
];
I hope it helped.
Try this, I am not sure whether this will work or not.
- (void)updateTableWithNewRowCount:(NSInteger)rowCount
andNewData:(NSArray *)newData {
// Save the tableview content offset
CGPoint tableViewOffset = [self.messagesTableView contentOffset];
// Turn of animations for the update block
// to get the effect of adding rows on top of TableView
[UIView setAnimationsEnabled:NO];
NSMutableArray *rowsInsertIndexPath = [[NSMutableArray alloc] init];
int heightForNewRows = 0;
for (NSInteger i = 0; i < rowCount; i++) {
NSIndexPath *tempIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
[rowsInsertIndexPath addObject:tempIndexPath];
// [self.messages insertObject:[newData objectAtIndex:i] atIndex:i];
[self addMessage:[newData objectAtIndex:i]];
heightForNewRows = heightForNewRows + [self heightForCellAtIndexPath:tempIndexPath];
}
tableViewOffset.y += heightForNewRows;
[UIView setAnimationsEnabled:YES];
[self.messagesTableView setContentOffset:tableViewOffset animated:NO];
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self.messagesTableView beginUpdates];
[self.messagesTableView insertRowsAtIndexPaths:rowsInsertIndexPath
withRowAnimation:UITableViewRowAnimationNone];
[self.messagesTableView endUpdates];
});
}
Note: You might require to update rowsInsertIndexPath reference to use inside the block (__block type).

ios: UITableView reloadData not working until scroll

I've noticed a few questions similar to this one on SO but they all seem to end in the person was calling
[self.tableView reloadData]
Outside of the main thread.
My problem is, I'm calling this on the main UI thread and I still don't get the new rows loaded until I like tap the screen or scroll a little bit. Here's what I'm doing: When I enter my ViewController the table displays fully and correctly. After pressing a button on the view I call my external db and load in some new rows to be added to the table. After adding these to my data source, I call reloadData and all of the new rows are blank. The couple rows that still fit on the screen that were already part of the table still show, but nothing new. When I touch the screen or scroll a little bit all of the new cells pop up.
Here's my code starting from when my call to the server finishes:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if(connection == self.searchConnection) {
..... some code ......
if(successful) {
// Adding new data to the datasource
[self.locations removeObjectAtIndex:[[NSNumber numberWithInt:self.locations.count - 1] integerValue]];
[self.cellTypeDictionary removeAllObjects];
for(int i = 0; i < [jsonLocations count]; ++i) {
NSDictionary *locationDictionary = jsonLocations[i];
BTRLocation *location = [BTRLocation parseLocationJSONObject:locationDictionary];
[self.locations addObject:location];
}
if(self.currentNextPageToken != nil && ![self.currentNextPageToken isEqual:[NSNull null]] && self.currentNextPageToken.length > 0) {
BTRLocation *fakeLocation = [[BTRLocation alloc] init];
[self.locations addObject:fakeLocation];
}
[self determineCellTypes];
if([self.locations count] < 1) {
self.emptyView.hidden = NO;
} else {
... some code .....
if(self.pendingSearchIsNextPageToken) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[[NSNumber numberWithInt:countBefore] integerValue]
inSection:[[NSNumber numberWithInt:0] integerValue]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
});
} else {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[[NSNumber numberWithInt:0] integerValue]
inSection:[[NSNumber numberWithInt:0] integerValue]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
});
}
}
} else {
if(invalidAccessToken) {
// TODO: invalidAccessToken need to log out
}
}
You can see I even added
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
});
Even though I know connectionDidFinishLoading is called on the main thread. But I figured I'd just try it anyway.
I can see no reason for why this isn't working.
the proper way is:
[self.tableView reloadData];
__weak MyViewController* weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
__strong MyViewController *strongSelf = weakSelf;
[strongSelf.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
});
Sometimes I found that you add after the reloadData:
[self.tableView layoutIfNeeded];

IOS Insert Multiple Sections on load more

Having a hard time figuring out how to add additional sections to my tableview when a user reaches the end of the currently loaded content. My table is setup in such a way that each piece of content is givien a section in the tableview and I have no clue how to go about adding say 50 new section to my table using a library like this :
https://github.com/samvermette/SVPullToRefresh
- (void)insertRowAtBottom {
__weak SVViewController *weakSelf = self;
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[weakSelf.tableView beginUpdates];
[weakSelf.dataSource addObject:[weakSelf.dataSource.lastObject dateByAddingTimeInterval:-90]];
[weakSelf.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:weakSelf.dataSource.count-1 inSection:0]] withRowAnimation:UITableViewRowAnimationTop];
[weakSelf.tableView endUpdates];
[weakSelf.tableView.infiniteScrollingView stopAnimating];
});
}
First of I made my array Mutable so that I can just add objects to it. Then you also have to reload the tableview because you are changing the number of myArray.count I looked up what an NSIndexSet was and I made one for my array so I could set new sections for my tableview (add on the bottom or top) this is the full "block of code" that I used.
//Reload the tableview and update the array count
[self.tableView reloadData];
//Start the update
[weakSelf.tableView beginUpdates];
//Created the NSIndexSet to go into the next method
NSIndexSet *indexSet = [self.games indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop){
return [obj isKindOfClass:[NSString class]];
}];
//Inserting the new sections
[weakSelf.tableView insertSections:indexSet withRowAnimation:UITableViewRowAnimationTop];
[weakSelf.tableView endUpdates];
//Stop the Animation
[weakSelf.tableView.pullToRefreshView stopAnimating];
If you do not reload the tableview your tableview WILL NOT update. It may even crash because there is some weird error that appears.

Resources