在视图出现之前如何在iPhone上滚动到UITableView的底部


136

我有一个UITableView填充有可变高度的单元格。当视图推入视图时,我希望表格滚动到底部。

我目前有以下功能

NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[log count]-1 inSection:0];
[self.table scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];

log是一个可变数组,其中包含组成每个单元格内容的对象。

上面的代码可以正常工作,viewDidAppear但是不幸的是,副作用是在视图第一次出现时显示表格的顶部,然后跳到底部。我希望它table view可以在出现之前滚动到底部。

我试图滚动中viewWillAppearviewDidLoad,但在这两种情况下的数据没有被加载到表并且都抛出异常。

任何指导将不胜感激,即使只是告诉我我所拥有的就是所有可能的情况。

Answers:


148

我相信

 tableView.setContentOffset(CGPoint(x: 0, y: CGFloat.greatestFiniteMagnitude), animated: false)

会做你想要的。


14
太好了,谢谢。我创建了一个具有足够高的Y值的CGPoint,它将使其始终显示在底部。视图加载完成后,我可以使用(self.table.contentSize.height-self.table.frame.size.height)以相同的方法移至底部
acqu13sce 2010年

8
虽然这是一个完美的答案,但因为我们不需要计算多少个单元格,表格视图的高度等。但是我要指出的是,我们需要在重新加载表格视图之前调用此方法……如果我们之后写这个[table reloadData];
Fahim Parkar 2015年

4
在ios 10-12上不起作用-表格第一次消失
Vyachaslav Gerchicov

2
或者只是滚动到CGPoint(x: 0, y: tableView.contentSize.height)
琥珀色K

5
kes !!!使表消失:-o。最好使用[self.table scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animation:NO];
卡尔·海因

122

我认为最简单的方法是:

if (self.messages.count > 0)
{
    [self.tableView 
        scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.messages.count-1 
        inSection:0] 
        atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}

Swift 3版本:

if messages.count > 0 {
    userDefinedOptionsTableView.scrollToRow(at: IndexPath(item:messages.count-1, section: 0), at: .bottom, animated: true)
}

3
如果单元格比UITableView高,则
不起作用

3
单元格比UITableView高吗?从来没有听说过这样的用例。
Chamira Fernando

@ChamiraFernando,这是最简单的方法:)
AITAALI_ABDERRAHMANE

请注意,将替换messages.count为已经实现的可能是有意义的myTableView.dataSource!.tableView(myTableView, numberOfRowsInSection: 0)。是的,它更长,但是可以避免代码重复。您还需要处理可选的dataSource(不要像本示例中那样强行打开包装)。
Nikolay Suvandzhiev

@ChamiraFernando我知道这个问题很旧,但是仅仅因为您从未见过,这并不意味着它不会发生。为了回答您的问题,Foursquare之类的应用可能会遇到这种情况,即用户撰写评论。单元格的高度大于表格视图的高度。这是一个非常好的情况。
Caio

121

根据Jacob的回答,这是代码:

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

    if (self.messagesTableView.contentSize.height > self.messagesTableView.frame.size.height) 
    {
        CGPoint offset = CGPointMake(0, self.messagesTableView.contentSize.height - self.messagesTableView.frame.size.height);
        [self.messagesTableView setContentOffset:offset animated:YES];
    }
}

在iOS 11上,您应该使用调整后的表格视图框架高度:UIEdgeInsetsInsetRect(self.messagesTableView.frame, self.messagesTableView.safeAreaInsets).height
Slav

41

如果需要滚动到内容的精确末尾,可以这样进行:

- (void)scrollToBottom
{
    CGFloat yOffset = 0;

    if (self.tableView.contentSize.height > self.tableView.bounds.size.height) {
        yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height;
    }

    [self.tableView setContentOffset:CGPointMake(0, yOffset) animated:NO];
}

7
可与自动布局一起使用,但是从viewDidLayoutSubviews调用此方法很重要
Omaty 2015年

你能解释一下为什么我们需要做到这一点: yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height; ?谢谢。
Unheilig 2015年

3
@Unheilig如果您要滚动到self.tableView.contentSize.height表格视图的内容,则可能不可见,因为您滚动到了内容下方。因此,您必须滚动到表格视图末尾上方的一个“可见表格视图间隙”。
汉斯

31

我正在使用自动版式,但没有答案对我有用。这是我最终解决方案:

@property (nonatomic, assign) BOOL shouldScrollToLastRow;


- (void)viewDidLoad {
    [super viewDidLoad];

    _shouldScrollToLastRow = YES;
}


- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    // Scroll table view to the last row
    if (_shouldScrollToLastRow)
    {
        _shouldScrollToLastRow = NO;
        [self.tableView setContentOffset:CGPointMake(0, CGFLOAT_MAX)];
    }
}

1
这几乎对我有用,但是当我的表数据从外部API加载时,出现了奇怪的图形故障。就我而言,setContentOffset在获取数据并重新加载tableview时是否需要在其他时间调用?
jmoz 2014年

尝试在请求的完成处理程序中设置偏移量。
RaffAl 2014年

2
这在ios 10中不起作用-仅显示具有黑色背景的表格
RunLoop

2
设置初始内容偏移时使用的不是CGFLOAT_MAX我使用的contentSize.height - frame.height + contentInset.bottom。使用CGFLOAT_MAX似乎对我造成了困扰。
Baza207 '18

23

通过@JacobRelkin接受的解决方案并没有为我在的iOS 7.0中使用自动布局工作。

我有一个自定义子类,UIViewController并添加了一个实例变量_tableView作为其的子视图view。我_tableView使用自动版面定位。我尝试在viewDidLoad甚至在末尾调用此方法viewWillAppear:。两者都不起作用。

因此,我将以下方法添加到的自定义子类中UIViewController

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

[self tableViewScrollToBottomAnimated:NO]viewDidLoad工作结束时打电话。不幸的是,它还会导致tableView:heightForRowAtIndexPath:每个单元被调用三次。


23

这是我在Swift 2.0中实现的扩展。tableview加载后应调用以下函数:

import UIKit

extension UITableView {
    func setOffsetToBottom(animated: Bool) {
        self.setContentOffset(CGPointMake(0, self.contentSize.height - self.frame.size.height), animated: true)
    }

    func scrollToLastRow(animated: Bool) {
        if self.numberOfRowsInSection(0) > 0 {
            self.scrollToRowAtIndexPath(NSIndexPath(forRow: self.numberOfRowsInSection(0) - 1, inSection: 0), atScrollPosition: .Bottom, animated: animated)
        }
    }
}

3
这比使用内容大小更好。对于Swift3。如果self.numberOfRows(inSection:0)> 0 {self.scrollToRow(at:IndexPath.init(row:self.numberOfRows(inSection:0)-1,section:0),at:.bottom,动画:动画)}
Soohwan公园

如果scrollToLastRow这个方法不能完美工作,那么只需添加self.layoutIfNeeded()就可以了!
Yogesh Patel

16

细节

  • Xcode 8.3.2,Swift 3.1
  • Xcode 10.2(10E125),Swift 5

import UIKit

extension UITableView {
    func scrollToBottom(animated: Bool) {
        let y = contentSize.height - frame.size.height
        if y < 0 { return }
        setContentOffset(CGPoint(x: 0, y: y), animated: animated)
    }
}

用法

tableView.scrollToBottom(animated: true)

完整样本

不要忘记粘贴解决方案代码!

import UIKit

class ViewController: UIViewController {

    private weak var tableView: UITableView?
    private lazy var cellReuseIdentifier = "CellReuseIdentifier"

    override func viewDidLoad() {
        super.viewDidLoad()
        let tableView = UITableView(frame: view.frame)
        view.addSubview(tableView)
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
        self.tableView = tableView
        tableView.dataSource = self
        tableView.performBatchUpdates(nil) { [weak self] result in
            if result { self?.tableView?.scrollToBottom(animated: true) }
        }
    }
}

extension ViewController: UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 100
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier, for: indexPath )
        cell.textLabel?.text = "\(indexPath)"
        return cell
    }
}

15

实际上,快速地做到这一点的“迅捷”方法是:

var lastIndex = NSIndexPath(forRow: self.messages.count - 1, inSection: 0)
self.messageTableView.scrollToRowAtIndexPath(lastIndex, atScrollPosition: UITableViewScrollPosition.Bottom, animated: true)

对我来说很完美。


9

我希望表加载框架中显示的表末尾。我发现使用

NSIndexPath *scrollIndexPath = [NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0];
[[self tableView] scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];

之所以不起作用,是因为当工作台高度小于框架高度时出现错误。请注意,我的表只有一部分。

对我有用的解决方案是在viewWillAppear中实现以下代码:

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// on the initial cell load scroll to the last row (ie the latest Note)
if (initialLoad==TRUE) {
    initialLoad=FALSE; 
    NSIndexPath *scrollIndexPath = [NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0];
    [[self tableView] scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
        CGPoint offset = CGPointMake(0, (1000000.0));
        [self.tableView setContentOffset:offset animated:NO];
    }
}

BOOL ivar initialLoad在viewDidLoad中设置为TRUE。


您是否需要打电话scrollToRowAtIndexPath?之后您已经在打电话setContentOffset,这可能会使第一个电话变得毫无意义。
卡洛斯·P

9

对于Swift:

if tableView.contentSize.height > tableView.frame.size.height {
    let offset = CGPoint(x: 0, y: tableView.contentSize.height - tableView.frame.size.height)
    tableView.setContentOffset(offset, animated: false)
}


5

对于Swift 3(Xcode 8.1):

override func viewDidAppear(_ animated: Bool) {
    let numberOfSections = self.tableView.numberOfSections
    let numberOfRows = self.tableView.numberOfRows(inSection: numberOfSections-1)

    let indexPath = IndexPath(row: numberOfRows-1 , section: numberOfSections-1)
    self.tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.middle, animated: true)
}

3
这不能回答OP问题,这是从一开始就起作用的。您也应该致电super.viewDidAppear
streem

4

当然是Bug。可能在您使用的代码中的某个位置table.estimatedRowHeight = value(例如100)。用您认为行高可以达到的最高值替换该值,例如500。这应该结合以下代码解决问题:

//auto scroll down example
let delay = 0.1 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))

dispatch_after(time, dispatch_get_main_queue(), {
    self.table.scrollToRowAtIndexPath(NSIndexPath(forRow: self.Messages.count - 1, inSection: 0), atScrollPosition: UITableViewScrollPosition.Bottom, animated: false)
})

4

经过很多摆弄后,这对我有用:

var viewHasAppeared = false

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if !viewHasAppeared { goToBottom() }
}

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    viewHasAppeared = true
}

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

事实证明,该密钥并没有像某些人建议scrollToRowAtIndexPathdispatch_async那样包装在其中,而只是在调用之后layoutIfNeeded

我的理解是,在当前线程中调用scroll方法可确保显示视图之前立即设置滚动偏移量。当我调度到主线程时,在滚动生效之前的一瞬间就显示了视图。

(注意,您还需要viewHasAppeared标志,因为您不想goToBottom每次都viewDidLayoutSubviews被调用。例如,每当方向改变时都会被调用。)


3

使用上述解决方案,这将滚动到表的底部(仅在首先加载表内容的情况下):

//Scroll to bottom of table
CGSize tableSize = myTableView.contentSize;
[myTableView setContentOffset:CGPointMake(0, tableSize.height)];


2

如果必须在向下滚动之前异步加载数据,则可以采用以下解决方案:

tableView.alpha = 0 // We want animation!
lastMessageShown = false // This is ivar

viewModel.fetch { [unowned self] result in
    self.tableView.reloadData()

    if !self.lastMessageShown {
        dispatch_async(dispatch_get_main_queue()) { [unowned self] in
            if self.rowCount > 0 {
                self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: self.rowCount, inSection: 0), atScrollPosition: .Bottom, animated: false)
            }

            UIView.animateWithDuration(0.1) {
                self.tableView.alpha = 1
                self.lastMessageShown = true // Do it once
            }
        }
    }
}

2

在功能快捷3滚动到底

 override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(false)
        //scroll down
        if lists.count > 2 {
            let numberOfSections = self.tableView.numberOfSections
            let numberOfRows = self.tableView.numberOfRows(inSection: numberOfSections-1)
            let indexPath = IndexPath(row: numberOfRows-1 , section: numberOfSections-1)
            self.tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.middle, animated: true)
        }
    }

2
func scrollToBottom() {

    let sections = self.chatTableView.numberOfSections

    if sections > 0 {

        let rows = self.chatTableView.numberOfRows(inSection: sections - 1)

        let last = IndexPath(row: rows - 1, section: sections - 1)

        DispatchQueue.main.async {

            self.chatTableView.scrollToRow(at: last, at: .bottom, animated: false)
        }
    }
}

您应该添加

DispatchQueue.main.async {
            self.chatTableView.scrollToRow(at: last, at: .bottom, animated: false)
        }

否则它将不会滚动到底部。


2

使用此简单代码滚动tableView底部

NSInteger rows = [tableName numberOfRowsInSection:0];
if(rows > 0) {
    [tableName scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:rows-1 inSection:0]
                     atScrollPosition:UITableViewScrollPositionBottom
                             animated:YES];
}

1
基本上,这是OP说过的。此答案未解决OP的有关如何使其在viewWillAppear中正常工作的问题。
jk7

2

感谢Jacob的回答。如果有人对monotouch c#版本感兴趣的话,这真的很有帮助

private void SetScrollPositionDown() {
    if (tblShoppingListItem.ContentSize.Height > tblShoppingListItem.Frame.Size.Height) {
        PointF offset = new PointF(0, tblShoppingListItem.ContentSize.Height - tblShoppingListItem.Frame.Size.Height);
        tblShoppingListItem.SetContentOffset(offset,true );
    }
}

1

在iOS中,这对我来说效果很好

CGFloat height = self.inputTableView.contentSize.height;
if (height > CGRectGetHeight(self.inputTableView.frame)) {
    height -= (CGRectGetHeight(self.inputTableView.frame) - CGRectGetHeight(self.navigationController.navigationBar.frame));
}
else {
    height = 0;
}
[self.inputTableView setContentOffset:CGPointMake(0, height) animated:animated];

需要从 viewDidLayoutSubviews


1

[self.tableViewInfo scrollRectToVisible:CGRectMake(0,self.tableViewInfo.contentSize.height-self.tableViewInfo.height,self.tableViewInfo.width,self.tableViewInfo.height)动画:是];


1

可接受的答案不适用于我的表(数千行,动态加载),但以下代码有效:

- (void)scrollToBottom:(id)sender {
    if ([self.sections count] > 0) {
        NSInteger idx = [self.sections count] - 1;
        CGRect sectionRect = [self.tableView rectForSection:idx];
        sectionRect.size.height = self.tableView.frame.size.height;
        [self.tableView scrollRectToVisible:sectionRect animated:NO];
    }
}



0

在Swift中,您只需要

self.tableView.scrollToNearestSelectedRowAtScrollPosition(UITableViewScrollPosition.Bottom, animated: true)

使它自动滚动到底部


0

在swift 3.0中,如果要转到表格视图的任何特定单元格,请更改单元格索引值,例如更改“ self.yourArr.count”值。

self.yourTable.reloadData()
self.scrollToBottom() 
func scrollToBottom(){
    DispatchQueue.global(qos: .background).async {
        let indexPath = IndexPath(row: self.yourArr.count-1, section: 0)
        self.tblComment.scrollToRow(at: indexPath, at: .bottom, animated: true)
    }
}

0

我相信旧的解决方案不适用于swift3。

如果您知道表格中的行数,则可以使用:

tableView.scrollToRow(
    at: IndexPath(item: listCountInSection-1, section: sectionCount - 1 ), 
    at: .top, 
    animated: true)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.