cannyboy
cannyboy

Reputation: 24426

Scroll immediately to row in table before view shows

A view with a table gets pushed onto the screen and I want it to scroll to a certain row in the table before the screen actually displays. I use this code within the final viewcontroller.

NSIndexPath *scrollToPath = [NSIndexPath indexPathForRow:5 inSection:0]; 
[theTable scrollToRowAtIndexPath:scrollToPath atScrollPosition:UITableViewScrollPositionTop animated:NO];

When I put it in viewDiDAppear method, then it briefly flashes from the intial position of the table (at the top) to the row I want. I don't want it to show the initial position. If I put it in viewDidLoad or viewWillAppear then it crashes with a NSRangeException, presumably because the table isn't set up yet.

How would I get it to scroll without showing the initial position?

Upvotes: 13

Views: 8660

Answers (8)

Andrew Bogaevskyi
Andrew Bogaevskyi

Reputation: 2615

Needs to scroll to cell before table view appears on the screen, but after table view loads data. Use this in the viewDidLoad doesn't work for me.

This code works well to me:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    DispatchQueue.main.async { [weak self]
        let indexPath = IndexPath(row: rowIndex, section: sectionIndex)
        self?.tableView.scrollToRow(at: indexPath, at: .middle, animated: false)
    }
}

Tested on iOS 14.1.

It works in viewDidAppear even without DispatchQueue.main.async but scrolling become visible to users. It is not a good option I think.

Upvotes: 3

Ankur Teotia
Ankur Teotia

Reputation: 227

i had the exact same problem, after trying everything, this worked, the key is if you're using autolayout , you must write scrollToBottom code in viewDidLayoutSubviews

initialize scrollToBottom to true and then do this

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    // Scroll table view to the last row
    [self scrollToBottom];
}

-(void)scrollToBottom {
    if (shouldScrollToLastRow)
    {
        CGPoint bottomOffset = CGPointMake(0, self.tableView.contentSize.height - self.tableView.bounds.size.height);
        [self.tableView setContentOffset:bottomOffset animated:NO];
    } }

doing this will ensure you're almost at the bottom of you're tableView but might not be at the very bottom as its impossible to know the exact bottom offset when you're at the top of the tableView, so after that we can implement scrollViewDidScroll

-(void)scrollViewDidScroll: (UIScrollView*)scrollView
{
    float scrollViewHeight = scrollView.frame.size.height;
    float scrollContentSizeHeight = scrollView.contentSize.height;
    float scrollOffset = scrollView.contentOffset.y;

    // if you're not at bottom then scroll to bottom
    if (!(scrollOffset + scrollViewHeight == scrollContentSizeHeight))
    {
        [self scrollToBottom];
    } else {
    // bottom reached now stop scrolling
        shouldScrollToLastRow = false;
    }
}

Upvotes: 4

Tianyu Wang
Tianyu Wang

Reputation: 154

You can use GCD to dispatch the scroll into the next iteration of main run loop in viewDidLoad to achieve this behavior. The scroll will be performed before the table view is showed on screen, so there will be no flashing.

- (void)viewDidLoad {
   dispatch_async (dispatch_get_main_queue (), ^{
        NSIndexPath *indexPath = YOUR_DESIRED_INDEXPATH;
        [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
    }];
}

Upvotes: 2

Gago
Gago

Reputation: 307

Calling scrollToRowAtIndexPath: in viewWillAppear: does not work for me because tableView is not loaded yet. But I could solve this with calling scrollToRowAtIndexPath: after some delay.

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    [self performSelector:@selector(scrollToCell) withObject:nil afterDelay:0.1];
}

- (void) scrollToCell
{
    [_tableView reloadData];
    NSIndexPath *scrollToPath = [NSIndexPath indexPathForRow:5 inSection:0];
    [_tableView scrollToRowAtIndexPath:scrollToPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
}

Hope it will help someone.

Upvotes: 4

Sam Paul
Sam Paul

Reputation: 1

in iOS 8 and above [self.tableView reloadData] in viewWillAppear before scrollToRowAtIndexPath does not work, you have to do reload in viewDidAppear as given below:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:YES];

    [self.tableView reloadData];

     [self.tableView scrollToRowAtIndexPath:[[NSIndexPath indexPathForRow:18 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];

}

Note: you have to use UITableViewScrollPositionTop, otherwise the scrolling will not be as expected and may scroll to the bottom and even to blank areas after the last row if you specify scrollToRow as last row.

I think in iOS 8 and above the first time the tableView is loaded it does not load all the data used to construct the table, that is the reason you have to use [tableView reload] which I think is a bit weird.

Upvotes: -1

cannyboy
cannyboy

Reputation: 24426

Thanks to Shaggy and Dying Cactus for pointing me in the right direction. The answer is to load the table and scroll in viewWillAppear:

-(void)viewWillAppear:(BOOL)animated
{
    [theTable reloadData];
    NSIndexPath *scrollToPath = [NSIndexPath indexPathForRow:5 inSection:0]; 
    [theTable scrollToRowAtIndexPath:scrollToPath atScrollPosition:UITableViewScrollPositionTop animated:NO];   
}

Upvotes: 15

eric
eric

Reputation: 391

I just finished wrestling with this. Mine was adding a search bar to the top of the list, initially tucked under the top... ala some of the core apps. I was actually going to ask this same question!

I fear to offer this up, as it seems those who offer things up get pounded down.. but... I was surprised that there was not an easy solution... so I ended up doing this:

I use ABTableViewCell (you can google it) for custom drawn cells (nice and fast!), and when I get called to DRAW the second row (you could do this in your customly drawn cells without ABTableViewCell), I set it there, with a single fire semaphore:

if ( self.mOnlyOnce == NO && theRow > 1 ) {
    self.mOnlyOnce = YES;
    [self.mParentTable scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1] atScrollPosition:UITableViewScrollPositionTop animated:NO];
}

(choose your proper row/section, as suits you... if they were in the same section, you'd probably be setting row to something other than 0)

If you hate my solution (as I can't comment yet), please do me the favor of just leaving it at zero & letting a better solution come to the top.

Oh, also, there is an entry about hiding your search at the top... but mine was already done as a custom cell... here is that link.

enjoy

Upvotes: 1

Shaggy Frog
Shaggy Frog

Reputation: 27601

If I put it in viewDidLoad or viewWillAppear then it crashes with a NSRangeException, presumably because the table isn't set up yet.

Why isn't the table set up yet? In which method are you setting it up?

Upvotes: 2

Related Questions