iOS UItableview scrollToRowAtIndexPath not working anymore - objective-c

This morning I just installed new Xcode which includes iOS 6.
I have a table view loaded with a plist file containing chapters and lines. Chapters define the sections.
The user selects chapter and line and the tableview is automatically scrolled to the correct position (in the viewDidLoad).
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:linePos inSection:chapterPos];
[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop animated:YES];
this works just great in iOS5 simulator.
Trying this in the iOS 6 simulator the scroll is not performed. I get no errors. I have checked, linePos and chapterPos receive correct values but the scroll is not performed.
Any ideas why ?

Objective-C:
[self.tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
NSIndexPath *rowIndexPath = [NSIndexPath indexPathForRow:3 inSection:0];
[self.tableView scrollToRowAtIndexPath:rowIndexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
});
Swift:
tableView.reloadData()
DispatchQueue.main.async {
let indexPath = IndexPath(row: linePos, section: chapterPos)
self.tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}

For recent versions of iOS, please read Fyodor Volchyok's answer. Note that it's not marked as the accepted answer simply because at the time the question was first asked (Sept. 2012), the current answer was the working solution.
More recent versions of iOS also got the same problem which is now solved by Fyodor Volchyok's answer, so you should +1 his answer at that moment.
I found the answer. I have to first reload the data in the tableview
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:linePos inSection:chapterPos];
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop animated:YES];
Even though I found the answer I don't know why it is working in iOS5 and not in iOS6.
EDIT
Perhaps I should add that even though it was working, I was still having a problem in displaying the last row and posted a question for that
UItableview scrollToRowAtIndexPath not displaying last row correctly
As #Raj also asked for it, I should say that I was triggering that in the viewDidLoad. To correct the problem of the last row not displaying correctly I had to put it in the viewDidAppear.

This works in iOS 12 and 13:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.tableView.scrollToRow(at: IndexPath(row: 0, section: 1), at: .bottom, animated: true)
}

I ran into another issue (probably a bug) with scrollToRowAtIndexPath specifically on an iPhone X running ios11. My table has a few hundred sections and in collapsed mode ~10 would fit in the visible screen. As the indexPath got deeper, the scrolling gradually fell behind.
For example, when I wanted the search to find the item in row 30, the ScrollPositionTop would have one additional row before the actual row I expect to be at the top.
And as I tested searching for deeper rows, it started falling behind even more where for say anything past 100 rows deep or so, the expected row did not even come in the visible area.
The workaround I found so far is to say animated:NO for the scrolling within dispatch_async, then it works without any glitches.

I'm adding this answer as an addition to Fyodor Volchyok's answer. I also found that dispatching solves the issue. I was able to find a workaround that doesn't dispatch.
self.tableView.reloadData()
let index = // the desired index path
// For some reason, requesting the cell prior to
// scrolling was enough to workaround the issue.
self.tableView.cellForRowAtIndexPath(index)
self.tableView.scrollToRowAtIndexPath(index, atScrollPosition: .Top, animated: false)

After iOS7 the property automaticallyAdjustsScrollViewInsets of UIViewController default is YES. It will cause system to adjust the contentOffset of tableView when the view controller pushed. Even you call [self.tableView scrollToRowAtIndexPath:rowIndexPath atScrollPosition:UITableViewScrollPositionNone animated:NO]; in the viewWillAppear. The contentOffset also will be changed by system after viewWillAppear. So my solution is:
- (void)viewDidLoad {
[super viewDidLoad];
self.automaticallyAdjustsScrollViewInsets = NO;
/// any other codes
}
- (void)viewWillLayoutSubviews {
self.tableView.contentInset = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, 0, 0);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// change tableView data source
[self.tableView reloadData];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[self.dataSourceArray count] - 1 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionNone animated:NO];
}

One more possible workaround is to call layoutIfNeeded before calling scrollToRowAtIndexPath.
[self.view layoutIfNeeded];
[self.tableView scrollToRowAtIndexPath...];

It worked for me in ios11
self.tableView.estimatedRowHeight = 0;
self.tableView.estimatedSectionFooterHeight = 0;
self.tableView.estimatedSectionHeaderHeight = 0
dispatch_async(dispatch_get_main_queue(), ^{
NSInteger numberOfSections = self.tableView.numberOfSections;
if (numberOfSections > 0)
{
NSInteger lastSection = numberOfSections - 1;
NSInteger lastRowInLastSections = [self.tableView numberOfRowsInSection:lastSection] - 1;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:lastRowInLastSections inSection:lastSection];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:isAnimated];
}
});

Fyodor Volchyok's answer in Swift:
tableView.reloadData()
let indexPath = NSIndexPath(forRow: linePos, inSection: chapterPos)
// make sure the scroll is done after data reload was finished
dispatch_async(dispatch_get_main_queue()) {
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top, animated: true)
}

Related

CollectionView Should scroll to last cell

I am using collection view to showing the taken images of my app.I want as soon as the image taken the collection view should automatically move to last object in my collection view .My collection scrolls horizontally
Please help me to do this.
I have searched in google and i have found i have applied this code in my project
i have applied this code in my project in viewwillappear ,it showing error,
Please help me to do this
click here to see i am taking photo and showing in below ,ok ,in this image it has 3pictures ,if i again take a picture 4th picture will be there in collection view ,if user want to se the 4th picture means ,user want scroll by manually and can see it...
Here is my code:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[self navigationController] setNavigationBarHidden:NO animated:YES];
[session startRunning];
NSInteger section = [self numberOfSectionsInCollectionView:self.collection_View] - 1;
NSInteger item = [self collectionView:self.collection_View numberOfItemsInSection:section] - 1;
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:item inSection:section];
}
I want automatically scroll to the last pictures in horizontally
Thanks in Advance !!!
Add the following line inside your viewWillAppear method code:
[collectionView scrollToItemAtIndexPath:lastIndexPath atScrollPosition:UICollectionViewScrollPositionBottom animated:YES];
Use this:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[self navigationController] setNavigationBarHidden:NO animated:YES];
[session startRunning];
NSInteger section = [self numberOfSectionsInCollectionView:self.collection_View] - 1;
NSInteger item = [self collectionView:self.collection_View numberOfItemsInSection:section] - 1;
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:item inSection:section];
[collectionView scrollToItemAtIndexPath:lastIndexPath atScrollPosition:UICollectionViewScrollPositionBottom animated:YES];
}
Here's the Swift 4.2 version of the accepted answer
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let section = numberOfSections(in: collectionView) - 1
let item = collectionView.numberOfItems(inSection: section) - 1
let lastIndexPath = IndexPath(item: item, section: section)
collectionView.scrollToItem(at: lastIndexPath, at: .bottom, animated: true)
}

ios - reloadData TableView & Core Data

I am having a bit of trouble with reloading my tableView which is set in a UIViewController.
It is populated with CoreData (This works)
It is also got a custom TableViewCell for some design purposes
Now normally what I would use is this : [_mainTableView reloadData];
That does not work this time.
Is this because of the custom Cell or is Core Data's FetchRequest which loads fine initially and shows the data when I relaunch the app??
I have seen several questions and tutorials on SO and Google, but none of them seem to work in my case.
I have also explored these methods:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
found here: beignUpdates
Any advice is appreciated.
Have you synthesized your table view? Please try this:
[self.mainTableView reloadData];
Hope, it'll work. And, since you reload it at viewWillAppear, please sure that neither it allocate new times nor it's delegate/datasource assign again, if it is then, first time, do something like that:
_mainTableView.delegate = nil;
_mainTableView.delegate = self;//similarly, for datasource...
And, you need to explicitly call your viewWillAppear(I suppose you're already doing this.)
Please let me know in any concern.
Thanks.
Thank you for the input, I finally got it sorted.
Instead of calling the [_mainTableView reloadData] I had to call the fetchmethod again.
if (_managedObjectContext == nil)
{
_managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSLog(#"After _managedObjectContext: %#", _managedObjectContext);
}
[self setupFetchedResultsController];

reloadData doesn't call cellforrowatindexpath

Maybe it's a similar beginning, but it's true.
first of all sorry if this isn't formatted correctly, first time doing this. I've been using stackoverflow to find help for a long time now and it's been very helpful (thank you all), but this is the first time I've posted a question of my own. This question has been asked many times, but when I call [self.tableView reloadTable] the methods numberOfSectionsInTableView and numberOfRowsInSection are called but not cellForRowAtIndexPath.
Every answer I've seen when searching has been a variation of:
The tableView is nil
numberOfRowsInSection is 0
tableView's delegate/data source not set. None of these are the case for me so I'm wondering what else could be wrong.
But I'm not sure 4. calling reloadTable on the wrong uiTableView. Or it's about some other false.
Now my APP is similar to dropbox,
first when we log into it, we get a file list(include directories) in the TableView.also, I added a toolbar in the bottom of the view by [self.navigationController.view addSubview:toolBar], when I touch the button item "refresh", it calls [self.tableView reloadData] and works well.
Second when we select a directory we will get a new file list table which is pushViewController by self.navigationController, but this time when we touch the "refresh", the statement [self.tableView reloadData] calls numberOfSections, numberOfRows, not cellForRowAtIndexPath
Any ideas as to why cellForRow's not being called the Second time? Thanks in advance.
FileListViewController.h
#interface FileListViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource>
FileListViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (isDir) {
FileListViewController *fileListViewController = [[FileListViewController alloc] init];
[self.navigationController pushViewController:fileListViewController animated:YES];
}
}
- (void)refresh
{
[Utilities refresh];//Utilities is my custom class.
[self viewDidLoad];
[self.tableView reloadData];
}
My return number of section and row in table view is not 0.
When I added NSLog(#"Calling reloadData on %#", self.tableView); into "refresh":
- (void)refresh
{
[Utilities refresh];//Utilities is my custom class.
[self viewDidLoad];
NSLog(#"Calling reloadData on %#", self.tableView);
[self.tableView reloadData];
}
Then it returns Calling reloadData on ; contentOffset: {0, 0}>. Delegate: FileListViewController, DataSource: FileListViewController
You should not manually call [self viewDidLoad]. This method is designed to be overridden, and is automatically called. For more information, please read this documentation.

Is it possible to refresh a single UITableViewCell in a UITableView?

I have a custom UITableView using UITableViewCells.
Each UITableViewCell has 2 buttons. Clicking these buttons will change an image in a UIImageView within the cell.
Is it possible to refresh each cell separately to display the new image?
Any help is appreciated.
Once you have the indexPath of your cell, you can do something like:
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPathOfYourCell, nil] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
In Xcode 4.6 and higher:
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[indexPathOfYourCell] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
You can set whatever your like as animation effect, of course.
I tried just calling -[UITableView cellForRowAtIndexPath:], but that didn't work. But, the following works for me for example. I alloc and release the NSArray for tight memory management.
- (void)reloadRow0Section0 {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
NSArray *indexPaths = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[indexPaths release];
}
Swift:
func updateCell(path:Int){
let indexPath = NSIndexPath(forRow: path, inSection: 1)
tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic) //try other animations
tableView.endUpdates()
}
reloadRowsAtIndexPaths: is fine, but still will force UITableViewDelegate methods to fire.
The simplest approach I can imagine is:
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
[self configureCell:cell forIndexPath:indexPath];
It's important to invoke your configureCell: implementation on main thread, as it wont work on non-UI thread (the same story with reloadData/reloadRowsAtIndexPaths:). Sometimes it might be helpful to add:
dispatch_async(dispatch_get_main_queue(), ^
{
[self configureCell:cell forIndexPath:indexPath];
});
It's also worth to avoid work that would be done outside of the currently visible view:
BOOL cellIsVisible = [[self.tableView indexPathsForVisibleRows] indexOfObject:indexPath] != NSNotFound;
if (cellIsVisible)
{
....
}
If you are using custom TableViewCells, the generic
[self.tableView reloadData];
does not effectively answer this question unless you leave the current view and come back. Neither does the first answer.
To successfully reload your first table view cell without switching views, use the following code:
//For iOS 5 and later
- (void)reloadTopCell {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
NSArray *indexPaths = [[NSArray alloc] initWithObjects:indexPath, nil];
[self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
}
Insert the following refresh method which calls to the above method so you can custom reload only the top cell (or the entire table view if you wish):
- (void)refresh:(UIRefreshControl *)refreshControl {
//call to the method which will perform the function
[self reloadTopCell];
//finish refreshing
[refreshControl endRefreshing];
}
Now that you have that sorted, inside of your viewDidLoad add the following:
//refresh table view
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventValueChanged];
[self.tableView addSubview:refreshControl];
You now have a custom refresh table feature that will reload the top cell. To reload the entire table, add the
[self.tableView reloadData]; to your new refresh method.
If you wish to reload the data every time you switch views, implement the method:
//ensure that it reloads the table view data when switching to this view
- (void) viewWillAppear:(BOOL)animated {
[self.tableView reloadData];
}
Swift 3 :
tableView.beginUpdates()
tableView.reloadRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
Here is a UITableView extension with Swift 5:
import UIKit
extension UITableView
{
func updateRow(row: Int, section: Int = 0)
{
let indexPath = IndexPath(row: row, section: section)
self.beginUpdates()
self.reloadRows(at: [indexPath], with: .automatic)
self.endUpdates()
}
}
Call with
self.tableView.updateRow(row: 1)
Just to update these answers slightly with the new literal syntax in iOS 6--you can use Paths = #[indexPath] for a single object, or Paths = #[indexPath1, indexPath2,...] for multiple objects.
Personally, I've found the literal syntax for arrays and dictionaries to be immensely useful and big time savers. It's just easier to read, for one thing. And it removes the need for a nil at the end of any multi-object list, which has always been a personal bugaboo. We all have our windmills to tilt with, yes? ;-)
Just thought I'd throw this into the mix. Hope it helps.
I need the upgrade cell but I want close the keyboard.
If I use
let indexPath = NSIndexPath(forRow: path, inSection: 1)
tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic) //try other animations
tableView.endUpdates()
the keyboard disappear

-[UITableView scrollToRowAtIndexPath:] scrolls, but goes back to 1st row

I have a grouped table view with textfields in the tableview. For the keyboard to not hide the textfields, in the textFieldShouldBeginEditing, I am calling the scrollToRowAtIndexPath method and setting the scroll position of the row being edited to the top of the tableview.
The scrolling does happen, but as soon as the keyboard appears (ie as soon as the textFieldShouldBeginEditing returns YES), the table scrolls back to its original position and displays the first row of the first section on top. I am not calling reloadTable after making a call to scrollToRowAtIndexPath.
The problem occurs only for row 4 and 5 (bdate and zip) password2 works as expected.
This is the code I am using to scroll to a particular row
if(textField == password2){
indPath = [NSIndexPath indexPathForRow:3 inSection:0];
[self.tableView scrollToRowAtIndexPath:indPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
return YES;
}
else if(textField == bdate){
indPath = [NSIndexPath indexPathForRow:4 inSection:0];
[self.tableView scrollToRowAtIndexPath:indPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
return YES;
}
else if(textField == zip){
indPath = [NSIndexPath indexPathForRow:5 inSection:0];
[self.tableView scrollToRowAtIndexPath:indPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
return YES;
}
Can someone please tell me what could be going wrong? Any insight would help.
Thanks
I suspect what is going on is that the table view is rubber banding back, just like a webview in Safari does when you run pass its bounds. The most likely reason for this is that when you pop up the keyboard it is on top of the table view, but the table view has not physically contracted, thus all the UITableViewCells fit within the partially obscured UITableView, and when some of them scroll off it rubber bands so they all are within it.
In order to test this try reducing the size of the list view so that you can see the whole thing when the keyboard is displayed, if the bug goes away you want to write code that dynamically changes the size as the keyboard animates in and out.
//For iOS 7
[self.TableView reloadData];
NSIndexPath *indexPat = [NSIndexPath indexPathForRow:1 inSection:1];
[self.TableView scrollToRowAtIndexPath:indexPat atScrollPosition:UITableViewScrollPositionTop animated:YES];