7
votes

Here is the problem:

I have a bunch of views that I put in a UIScrollView. The size and position of those subviews are defined by constraints. This works perfectly, and scrolling works too. So far so good.

However, I want my scrollview to scroll all the way to the bottom when I show the viewcontroller on screen for the first time, and this is where trouble starts. In order to know where the bottom is I need to know the position and size of the lowest element in my subviews. Should be easy too (since I have the reference to that UIView somewhere): get the frame of the UIView and voila.

I want to scroll the scrollview to the bottom before it appears on screen (so basically, in viewWillAppear:), but the constraints only get evaluated after viewWillAppear and before viewDidAppear: is called.

Getting the UIView frame in viewWillAppear gives me a zero sized CGRect. Doing the same in viewDidAppear gives me the correct CGRect. But viewDidAppear is too late for me, since the scrollview is on screen already so you see the content moving up.

Does anyone have a good solution for this? I tried putting the code in viewDidLayoutSubviews but that doesn't work either.

5
I have the same problem, did you find a solution ?Starscream
check below for a possible solutionJoris Mans

5 Answers

6
votes

The problem with viewWillAppear is that it is called before the layout happens. You have to adjust the scroll view's content offset after it is laid out - in viewDidLayoutSubviews method. in Here is my solution:

@property (nonatomic, assign) BOOL shouldScrollToBottom;


- (void)viewDidLoad
{
    [super viewDidLoad];

    _shouldScrollToBottom = YES;
}


- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    // Scroll table view to the last row
    if (_shouldScrollToBottom)
    {
        _shouldScrollToBottom = NO;
        [_scrollView setContentOffset:CGPointMake(0, CGFLOAT_MAX)];
    }
}
1
votes

This is what I did and it works for me (cell height computed by autolayout):

class MessagesTableViewController: UITableViewController
{
    var shouldScrollToBottom = false

    override func viewDidLoad() 
    {
        super.viewDidLoad()
        ...
        shouldScrollToBottom = true
    }

    override func viewDidLayoutSubviews()
    {
        super.viewDidLayoutSubviews()
        if(shouldScrollToBottom == true)
        {
            shouldScrollToBottom = false
            goToBottom()
        }
    }

    private func goToBottom()
    {
        if(data.count == 0)
        {
            return
        }
        let indexPath = NSIndexPath(forRow: tableView.numberOfRowsInSection(0)-1, inSection: 0)
        tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: false)
    }
}
1
votes

I was running into this problem too! The view didn't scroll to the desired page before viewDidAppear(). It had multiple subviews to be layed out by viewDidLayoutSubviews(), so doing it once in that function, like Thomas suggested, didn't work for me either. However, the viewDidLayoutSubviews() approach works, if you use a little trick:

You need to attempt to scroll to the desired point every time viewDidLayoutSubviews() is called during the creation of the view. Then, after the view is displayed, you don't want this to be called anymore. So you add a control variable that is changed in viewDidAppear(), after the initial layout. Here's the full example:

var needsFirstScroll = true

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if(needsFirstScroll) {
        var frame = CGRect() // placeholder, do your frame calculation here
        scrollView.scrollRectToVisible(frame, animated: false)
    }
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    needsFirstScroll = false
}
0
votes

The solution I finally came up with was to:

  • Store all subviews of the scrollview in 1 contentview and not directly as child of the scrollview
  • observe when the bounds of that contentview change
  • each time they change do a scrollRectToVisible to the bottom of the scrollview
  • disable the auto-scroll-to-bottom as soon as a scrollViewWillBeginDragging delegate call is detected so the auto-scrolling stops when the user starts moving the scrollview
-1
votes

I found a solution to this problem, this might be useful to future visitors. You need to load the tableView in viewWillAppear before trying to scroll.

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

    [self.tableView reloadData];
    [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.tableView.numberOfRowsInSection:0] inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:animated];
}