简体   繁体   English

UITableView - 在viewDidAppear之前滚动到底部

[英]UITableView - Scroll to bottom before viewDidAppear

In case it's not obvious by the title, what I want should be simple, for the tableView to start scrolled all the way to the bottom when the users first sees it ( before he sees it, and without animations ). 如果标题不明显,我想要的应该是简单的,因为当用户第一次看到它时( 在他看到它之前,没有动画 tableView开始一直滚动到底部。

So, I know that this has been answered a few times, but none of those solutions seem to work right now. 所以,我知道这已经得到了几次回答,但这些解决方案似乎都没有发挥作用。 To give some context, I'm using Swift, autolayout and latest version of iOS as of today. 为了给出一些上下文,我现在使用的是Swift,autolayout和最新版本的iOS。

Constraints 约束

There are some things I need to support: 我需要支持一些事情:

  1. load it before user sees it (without animations, obviously). 在用户看到它之前加载它(显然没有动画)。
  2. dynamic cell heights, ie, their height is determined by a UILabel (like a messaging app - using autolayout in storyboard). 动态单元格高度,即它们的高度由UILabel(如消息传递应用程序 - 在故事板中使用自动布局)确定。
  3. My UITableView is inside a UIScrollView ( UIScrollView scrolls horizontally and UITableView scrolls vertically). 我的UITableView位于UIScrollViewUIScrollView水平滚动, UITableView垂直滚动)。
  4. The UITableView is in a childViewController , ie, I add it to the main UIViewController (which, for some reason, makes viewWillAppear not get called - viewDidAppear is still called). UITableView位于childViewController ,即我将它添加到主UIViewController (由于某种原因,使得viewWillAppear不被调用 - 仍然调用viewDidAppear )。

I'll make a summary of what I tried: 我将总结一下我的尝试:

As for implementations: 至于实施:

  1. tableView.contentOffset = CGPoint(x: 0, y: CGFloat.max

  2. tableView.scrollToRowAtIndexPath(indexPathForLastItem, atScrollPosition: .Bottom, animated: false)


    var contentOffset = tableView.contentOffset
    contentOffset.y = tableView.contentSize.height - tableView.bounds.size.height
    tableView.setContentOffset(contentOffset, animated: false)


    dispatch_async(dispatch_get_main_queue(), {
        // tried option 1., 2. and 3. here
    })

As for places I've tried to call those implementations: 至于我试图调用这些实现的地方:

  1. viewDidLoad
  2. viewWillAppear
  3. viewDidLayoutSubviews (only the first time it's called, I use a property to track that) viewDidLayoutSubviews (仅在第一次调用时,我使用属性来跟踪它)
  4. viewDidAppear (even though this wouldn't give me what I want) viewDidAppear (即使这不会给我我想要的)
  5. On my model's didSet 在我的模型的didSet

Before those I always called tableView.reloadData 在那之前我总是调用tableView.reloadData

What I DON'T want to do: 我不想做的事:

tableView.transform = CGAffineTransformMakeScale(1, -1)

+ +

cell.transform = CGAffineTransformMakeScale(1, -1)

(I'm assuming that if you think of suggesting this solution, it's because you know the hack I'm talking about. If you don't, then you won't be suggesting this, so you don't need to understand how it works) (我假设如果你想建议这个解决方案,那是因为你知道我正在谈论的黑客。如果你不这样做,那么你就不会建议这样做,所以你不需要了解如何有用)

One of the reasons why I don't want this one is because now I can't scroll to the top... 我不想要这个的原因之一是因为现在我无法滚动到顶部...

Problems I've noticed: 我注意到的问题:

  1. The tableView 's contentSize (as a UIScrollView subclass) changes when you scroll for the first time after it appeared. tableViewcontentSize (作为UIScrollView子类)在它出现后第一次滚动时会发生变化。 Yes, I meant contentSize , not contentOffset , and it changes more than once while you scroll. 是的,我的意思是contentSize ,而不是contentOffset ,滚动时它会多次更改。 After you've scrolled through the entire tableView once, it doesn't change anymore. 滚动浏览整个tableView一次后,它不再发生变化。
  2. Some people are saying it works for fewer cells (and to be honest, it was working for me at some point), so try it with at least 20 items. 有些人说它适用于较少的细胞(说实话,它在某些时候对我有用),所以试试至少20项。
  3. viewWillAppear doesn't get called, but viewDidAppear does. viewWillAppear不会被调用,但viewDidAppear会调用。

Any solutions (except for the one I mentioned I don't want) would be very much appreciated. 我非常感谢任何解决方案(除了我提到的我不想要的解决方案)。 Even hacky ones, as long as they don't break other stuff. 即使是hacky,只要他们不打破其他东西。

Just as a side note, scrollToRowAtIndexPath... does not scale, ie, it is too slow if you have, say, 2000 items. 正如旁注, scrollToRowAtIndexPath...不会缩放,例如,如果你有2000个项目它太慢了。 So Ideally I'd prefer a solution that scales. 所以理想情况下我更喜欢可扩展的解决方案。

EDITS AFTER ANSWERS: 回答后的编辑:

  1. After @bsmith11's answer I tried: 在@ bsmith11的回答之后我尝试了:

     private var called = false override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if !called { dispatch_async(dispatch_get_main_queue(), { self.tableView.setContentOffset(CGPoint(x: 0, y: self.tableView.bounds.height), animated: false) }) called = true } } 

    And it didn't work. 它没有用。

In viewDidLayoutSubviews() , set the .contentOffset to tableView.bounds.height . viewDidLayoutSubviews() ,将.contentOffset设置为tableView.bounds.height You will need to kick this off in an async block to give the tableView time to load it's content. 您需要在异步块中启动它以使tableView时间加载它的内容。

dispatch_async(dispatch_get_main_queue()) { self.tableView.setContentOffset(tableView.bounds.height, animated: false) }

viewDidLayoutSubviews() can get called multiple times, so you probably want to make sure your code above only gets called once. viewDidLayoutSubviews()可以多次调用,因此您可能希望确保上面的代码只被调用一次。

Just tried with below code it works fine. 刚试过下面的代码就行了。

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(true)

      myArray = [["Data": "1234567890", "Height": 44],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "12345678901234567890", "Height": 88],["Data": "End of Table", "Height": 132]]

    let no = myArray.count-1

    let lastIndex = NSIndexPath(forRow: no, inSection: 0)

    table1.scrollToRowAtIndexPath(lastIndex, atScrollPosition: .Bottom, animated: true)

}

Download sample code 下载示例代码

add the following method to custom subclass. 将以下方法添加到自定义子类。

 - (void)tableViewScrollToBottomAnimated:(BOOL)animated {
        NSInteger numberOfRows = [_tableView numberOfRowsInSection:0];
        if (numberOfRows) {
            [_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:numberOfRows-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:animated];
        }
    }

Calling [self tableViewScrollToBottomAnimated:NO] at the end of viewDidAppear works. viewDidAppear结束时调用[self tableViewScrollToBottomAnimated:NO]可以正常工作。 One concern is there Unfortunately, it also causes tableView:heightForRowAtIndexPath: to get called three times for every cell. 一个问题是不幸的是,它还导致tableView:heightForRowAtIndexPath:为每个单元tableView:heightForRowAtIndexPath:三次。

i had the exact same problem, after trying everything(same as you), this worked, the key is if you're using autolayout , you must write scrollToBottom code in viewDidLayoutSubviews 我有完全相同的问题,在尝试了所有东西(和你一样)后,这个工作,关键是如果你使用autolayout,你必须在viewDidLayoutSubviews中编写scrollToBottom代码

initialize scrollToBottom to true and then do this 将scrollToBottom初始化为true,然后执行此操作

- (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 这样做可以确保你几乎位于tableView的底部,但可能不会处于最底层,因为当你位于tableView的顶部时,不可能知道确切的底部偏移,所以之后我们可以实现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;
    }
}
private var called = false
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if !called {
        dispatch_async(dispatch_get_main_queue(), {
            self.tableView.setContentOffset(CGPoint(x: 0, y: self.tableView.bounds.height), animated: false)
        })
        called = true
    }
}

I tried this code with my UIScrollView. 我用我的UIScrollView尝试了这段代码。 And it works . 它有效 If I have dispatch_async removed it become not working. 如果我删除了dispatch_async它将无法正常工作。

Swift 3 code Swift 3代码

private var called = false
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if !called {
        DispatchQueue.main.async(execute: {
            self.tableView.setContentOffset(CGPoint(x: 0, y: self.tableView.bounds.height), animated: false)})
        called = true
    }
}

I think that calling [table setContentOffset:CGPointMake(0, CGFLOAT_MAX)] and table.estimatedRowHeight = 50(change your value); 我认为调用[table setContentOffset:CGPointMake(0, CGFLOAT_MAX)]table.estimatedRowHeight = 50(change your value); in ViewDidLoad is what you need. 在ViewDidLoad中你需要的。

  1. Uncheck "Automatic" tableview's properties ; 取消选中“自动”tableview的属性; row Height and Estimate 行高和估计

在此输入图像描述

  1. Add this code 添加此代码

     override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if scrollToBottomOnce == false { scrollToBottomOnce = true if self.items.count > 0 { self.tableView.setContentOffset(CGPoint(x: 0, y: CGFloat.greatestFiniteMagnitude), animated: false) } } } 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM