Cách cuộn xuống dưới cùng của UITableView trên iPhone trước khi chế độ xem xuất hiện


136

Tôi có một UITableViewcái được gắn với các ô có chiều cao thay đổi. Tôi muốn bảng cuộn xuống phía dưới khi chế độ xem được đẩy vào chế độ xem.

Tôi hiện có chức năng sau đây

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

log là một mảng có thể thay đổi chứa các đối tượng tạo nên nội dung của từng ô.

Các mã trên hoạt động tốt, viewDidAppeartuy nhiên điều này có tác dụng phụ đáng tiếc là hiển thị đầu bảng khi chế độ xem xuất hiện đầu tiên và sau đó nhảy xuống dưới cùng. Tôi thích nó nếu table viewcó thể được cuộn xuống phía dưới trước khi nó xuất hiện.

Tôi đã thử cuộn viewWillAppearviewDidLoadnhưng trong cả hai trường hợp, dữ liệu vẫn chưa được tải vào bảng và cả hai đều đưa ra một ngoại lệ.

Bất kỳ hướng dẫn nào cũng sẽ được đánh giá cao, ngay cả khi đó chỉ là một trường hợp cho tôi biết những gì tôi có là tất cả những gì có thể.

Câu trả lời:


148

Tôi tin rằng gọi

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

sẽ làm những gì bạn muốn.


14
Đó là hoàn hảo cảm ơn bạn. Tôi đã tạo một CGPoint với giá trị Y đủ cao sẽ khiến nó luôn hiển thị phía dưới. Khi chế độ xem đã được tải, tôi có thể sử dụng (
self.table.contentSize.height

8
Mặc dù đây là câu trả lời hoàn hảo, vì chúng ta không cần tính toán bao nhiêu ô, chiều cao của chế độ xem bảng, v.v. NHƯNG tôi sẽ chỉ ra rằng chúng ta cần gọi nó trước khi tải lại bảng xem ... Nó sẽ không hoạt động nếu chúng ta viết cái này sau[table reloadData];
Fahim Parkar

4
không hoạt động trên ios 10-12 - bảng chỉ đơn giản biến mất lần đầu tiên
Vyachaslav Gerchicov 7/12/18

2
Hay chỉ cần cuộn đến CGPoint(x: 0, y: tableView.contentSize.height)?
Amber K

5
Rất thích !!! Làm cho cái bàn biến mất: -o. Sử dụng tốt nhất [self.table scrollToRowAtIndexPath: indexPath atScrollPocation: UITableViewScrollPocationBottom hoạt hình: NO];
Carl Hine

122

Tôi nghĩ cách dễ nhất là đây:

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

Phiên bản Swift 3:

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

3
Nó không hoạt động nếu ô cao hơn UITableView
Olav Gausaker

3
Tế bào cao hơn UITableView? chưa bao giờ nghe một trường hợp sử dụng như vậy.
Chamira Fernando

@ChamiraFernando đây là cách dễ nhất :)
AITAALI_ABDERRAHMANE

Lưu ý rằng nó có thể có ý nghĩa để thay thế messages.countbởi đã được thực hiện myTableView.dataSource!.tableView(myTableView, numberOfRowsInSection: 0). Có nó dài hơn, nhưng nó có thể tránh lặp lại mã. Bạn cũng cần xử lý tùy chọn dataSource(không ép buộc như trong mẫu này).
Nikolay Suvandzhiev

@ChamiraFernando Tôi biết câu hỏi này đã cũ, nhưng chỉ vì bạn chưa bao giờ thấy, điều đó không có nghĩa là nó không xảy ra. Để trả lời câu hỏi của bạn, các ứng dụng như Foursquare có thể gặp tình huống này, nơi người dùng viết đánh giá. Chiều cao của ô lớn hơn chiều cao của mặt bàn. Đó là một tình huống hoàn toàn tốt đẹp.
Caio

121

Từ câu trả lời của Jacob , đây là mã:

- (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];
    }
}

Trên iOS 11, bạn nên sử dụng chiều cao khung xem bảng đã điều chỉnh:UIEdgeInsetsInsetRect(self.messagesTableView.frame, self.messagesTableView.safeAreaInsets).height
Slav

41

Nếu bạn cần cuộn đến cuối CHÍNH XÁC của nội dung, bạn có thể làm như thế này:

- (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
Hoạt động với tính năng tự động thanh toán, NHƯNG điều quan trọng là gọi phương thức này từ viewDidLayoutSubview
Omaty

Bạn có thể vui lòng giải thích lý do tại sao chúng ta cần phải làm điều này : yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height; ? Cảm ơn.
Unheilig

3
@Unheilig Nếu bạn cuộn đến self.tableView.contentSize.heightnội dung của chế độ xem bảng có thể không hiển thị, vì bạn cuộn bên dưới nội dung. Do đó, bạn phải cuộn đến một "khoảng cách xem bảng hiển thị" phía trên cuối của chế độ xem bảng.
Hans Một

31

Tôi đang sử dụng tính năng tự động thanh toán và không có câu trả lời nào phù hợp với tôi. Đây là giải pháp của tôi cuối cùng đã hoạt động:

@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
Điều này gần như làm việc với tôi nhưng tôi gặp một trục trặc đồ họa kỳ lạ trong khi dữ liệu bảng của tôi đang tải từ API bên ngoài. Trong trường hợp của tôi, tôi có cần gọi setContentOffsetở một số điểm khác khi dữ liệu đã được tải xuống và xem lại bảng không?
jmoz

Hãy thử thiết lập bù trong một trình xử lý hoàn thành yêu cầu của bạn.
RaffAl

2
Điều này không hoạt động trong ios 10 - chỉ đơn giản là hiển thị một bảng có nền màu đen
RunLoop

2
Thay vì sử dụng CGFLOAT_MAXtôi đã sử dụng contentSize.height - frame.height + contentInset.bottomkhi cài đặt bù nội dung ban đầu. Sử dụng CGFLOAT_MAXdường như để gây rối cho tôi.
Baza207

23

Các giải pháp được chấp nhận bởi @JacobRelkin không làm việc cho tôi trong iOS 7.0 sử dụng Auto Layout.

Tôi có một lớp con tùy chỉnh UIViewControllervà thêm một biến thể hiện _tableViewdưới dạng một khung nhìn con của nó view. Tôi định vị_tableView bằng Giao diện tự động. Tôi đã thử gọi phương thức này vào cuối viewDidLoadvà thậm chí trong viewWillAppear:. Không làm việc.

Vì vậy, tôi đã thêm phương thức sau vào lớp con tùy chỉnh của mình UIViewController .

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

Gọi [self tableViewScrollToBottomAnimated:NO]vào cuối viewDidLoadcông trình. Thật không may, nó cũng gây ra tableView:heightForRowAtIndexPath:được gọi ba lần cho mỗi tế bào.


23

Đây là một phần mở rộng mà tôi đã triển khai trong Swift 2.0. Các chức năng này nên được gọi sau khi tableviewđã được tải:

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
Điều này tốt hơn so với việc sử dụng kích thước nội dung. Đối với Swift3. if self.numberOfRows (inSection: 0)> 0 {self.scrollToRow (at: IndexPath.init (row: self.numberOfRows (inSection: 0) -1, phần: 0), tại: .bottom, hoạt hình: hoạt hình)
Công viên Soohwan

nếu scrollToLastRow phương thức này không hoạt động hoàn hảo thì chỉ cần thêm self.layout IfNeeded () và nó hoạt động hoàn hảo !!
Yogesh Patel

16

Chi tiết

  • Xcode 8.3.2, nhanh chóng 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)
    }
}

Sử dụng

tableView.scrollToBottom(animated: true)

Mẫu đầy đủ

Đừng quên dán mã giải pháp!

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

Trên thực tế, một cách "Swifter" để làm điều đó nhanh chóng là:

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

làm việc hoàn hảo cho tôi


9

Tôi muốn bảng tải với phần cuối của bảng được hiển thị trong khung. Tôi tìm thấy bằng cách sử dụng

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

không hoạt động vì nó báo lỗi khi chiều cao bảng nhỏ hơn chiều cao khung. Lưu ý bảng của tôi chỉ có một phần.

Giải pháp hiệu quả với tôi là triển khai đoạn mã sau trong 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 initLoad được đặt thành TRUE trong viewDidLoad.


Bạn có cần gọi scrollToRowAtIndexPathgì không? Bạn đã gọi setContentOffsetsau đó, điều đó có thể khiến cuộc gọi đầu tiên đó trở nên vô nghĩa.
Carlos P

9

Dành cho 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

Đối với 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
Điều này không trả lời câu hỏi OP, đây là những gì đã làm việc từ đầu. Ngoài ra, bạn nên gọi super.viewDidAppear
streem

4

Tất nhiên là một lỗi. Có thể ở đâu đó trong mã của bạn mà bạn sử dụng table.estimatedRowHeight = value(ví dụ 100). Thay thế giá trị này bằng giá trị cao nhất mà bạn nghĩ rằng chiều cao hàng có thể nhận được , ví dụ 500 .. Điều này sẽ giải quyết vấn đề kết hợp với mã sau:

//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

Sau nhiều lần nghịch ngợm, đây là điều làm việc cho tôi:

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()
}

Chìa khóa hóa ra không được bọc scrollToRowAtIndexPathbên trong dispatch_asyncnhư một số người đã đề xuất, mà chỉ cần thực hiện theo nó với một cuộc gọi đếnlayoutIfNeeded .

Tôi hiểu điều này là, gọi phương thức cuộn trong luồng hiện tại đảm bảo rằng phần bù cuộn được đặt ngay lập tức, trước khi chế độ xem được hiển thị. Khi tôi đang gửi đến luồng chính, chế độ xem sẽ được hiển thị ngay lập tức trước khi cuộn có hiệu lực.

(Ngoài ra NB bạn cần viewHasAppearedcờ vì bạn không muốn goToBottommỗi lần viewDidLayoutSubviewsđược gọi. Nó được gọi chẳng hạn mỗi khi thay đổi hướng.)


3

Sử dụng các giải pháp trên, điều này sẽ cuộn xuống cuối bảng của bạn (chỉ khi nội dung bảng được tải trước):

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

3

Trong Swift 3.0

self.tableViewFeeds.setContentOffset(CGPoint(x: 0, y: CGFLOAT_MAX), animated: true)

2

Nếu bạn phải tải dữ liệu không đồng bộ trước khi cuộn xuống, đây là giải pháp có thể:

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

Chức năng trên swift 3 cuộn xuống dưới cùng

 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)
        }
    }
}

bạn nên thêm

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

hoặc nó sẽ không cuộn xuống dưới cùng.


2

Sử dụng mã đơn giản này để cuộn bảng Xem dưới cùng

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

1
Đây là cơ bản những gì OP cho biết ông đã thử. Câu trả lời này không giải quyết câu hỏi của OP về cách làm cho nó hoạt động chính xác trong viewWillAppear.
jk7

2

Cảm ơn Jacob đã trả lời. thực sự hữu ích nếu có ai thú vị với phiên bản c # monotouch

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

Trong iOS, nó hoạt động tốt với tôi

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];

Nó cần được gọi từ viewDidLayoutSubviews


1

[self.tableViewInfo scrollRectToVisible: CGRectMake (0, self.tableViewInfo.contentSize.height-self.tableViewInfo.height, self.tableViewInfo.ference, self.tableViewInfo.height) hoạt hình: YES];


1

Câu trả lời được chấp nhận không hoạt động với bảng của tôi (hàng nghìn hàng, tải động) nhưng mã bên dưới hoạt động:

- (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];
    }
}

1

Không cần bất kỳ cuộn nào bạn chỉ có thể làm điều đó bằng cách sử dụng mã này:

[YOURTABLEVIEWNAME setContentOffset:CGPointMake(0, CGFLOAT_MAX)];

1

Nếu bạn đang thiết lập khung cho chế độ xem bảng theo chương trình, hãy đảm bảo bạn đang đặt khung chính xác.


0

Trong Swift, bạn chỉ cần

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

để làm cho nó tự động cuộn đến mông


0

Trong swift 3.0 Nếu bạn muốn đi bất kỳ ô nào của chế độ xem bảng Thay đổi chỉ mục ô Giá trị như thay đổi giá trị "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

Tôi tin rằng các giải pháp cũ không hoạt động với swift3.

Nếu bạn biết các hàng số trong bảng, bạn có thể sử dụng:

tableView.scrollToRow(
    at: IndexPath(item: listCountInSection-1, section: sectionCount - 1 ), 
    at: .top, 
    animated: true)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.