Bagaimana menggulir ke bagian bawah UITableView di iPhone sebelum tampilan muncul


139

Saya memiliki UITableViewyang diisi dengan sel dengan tinggi variabel. Saya ingin tabel di-scroll ke bawah saat view ditampilkan.

Saat ini saya memiliki fungsi berikut

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

log adalah larik yang bisa berubah berisi objek yang membentuk konten setiap sel.

Kode di atas berfungsi dengan baik viewDidAppearnamun ini memiliki efek samping yang tidak menguntungkan yaitu menampilkan bagian atas tabel saat tampilan pertama kali muncul dan kemudian melompat ke bawah. Saya lebih suka jika table viewbisa digulir ke bawah sebelum muncul.

Saya mencoba menggulir ke dalam viewWillAppeardan viewDidLoadtetapi dalam kedua kasus, data belum dimuat ke dalam tabel dan keduanya memberikan pengecualian.

Panduan apa pun akan sangat dihargai, bahkan jika itu hanya kasus memberi tahu saya apa yang saya miliki adalah semua yang mungkin.

Jawaban:


150

Saya percaya panggilan itu

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

akan melakukan apa yang kamu inginkan.


14
Sempurna terima kasih. Saya membuat CGPoint dengan nilai Y yang cukup tinggi yang akan membuatnya selalu menampilkan bagian bawah. Setelah tampilan dimuat, saya dapat menggunakan (self.table.contentSize.height - self.table.frame.size.height) untuk pindah ke bawah dengan metode yang sama
acqu13sce

8
Meskipun ini adalah jawaban yang sempurna, karena kita tidak perlu melakukan kalkulasi untuk berapa banyak sel, ketinggian tampilan tabel, dll. TAPI saya ingin menunjukkan bahwa kita perlu memanggil ini sebelum memuat ulang tampilan tabel ... Ini tidak akan berhasil jika kita tulis ini setelahnya[table reloadData];
Fahim Parkar

4
tidak berfungsi pada ios 10-12 - tabel menghilang begitu saja untuk pertama kalinya
Vyachaslav Gerchicov

2
Atau cukup gulir ke CGPoint(x: 0, y: tableView.contentSize.height)?
Amber K

6
Astaga !!! Menghilangkan meja: -o. Paling baik menggunakan [self.table scrollToRowAtIndexPath: indexPath atScrollPosition: UITableViewScrollPositionBottom animated: NO];
Carl Hine

124

Saya pikir cara termudah adalah ini:

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

Versi Swift 3:

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

3
Tidak berfungsi jika sel lebih tinggi dari UITableView
Olav Gausaker

3
Sel Lebih Tinggi dari UITableView? pernah mendengar kasus penggunaan seperti itu.
Chamira Fernando

@ChamiraFernando ini adalah cara termudah :)
AITAALI_ABDERRAHMANE

Perhatikan bahwa mungkin masuk akal untuk mengganti messages.countdengan yang sudah diterapkan myTableView.dataSource!.tableView(myTableView, numberOfRowsInSection: 0). Ya, ini lebih lama, tetapi mungkin menghindari pengulangan kode. Anda juga perlu menangani opsional dataSource(jangan buka paksa seperti dalam contoh ini).
Nikolay Suvandzhiev

@ChamiraFernando Saya tahu pertanyaan ini sudah lama, tetapi hanya karena Anda tidak pernah melihatnya, bukan berarti itu tidak terjadi. Untuk menjawab pertanyaan Anda, aplikasi seperti Foursquare mungkin mengalami situasi ini, di mana pengguna menulis ulasan. Tinggi sel lebih bagus dari ketinggian tableview. Ini situasi yang sangat baik.
Caio

121

Dari jawaban Yakub inilah kodenya:

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

Di iOS 11, Anda harus menggunakan tinggi bingkai tampilan tabel yang disesuaikan:UIEdgeInsetsInsetRect(self.messagesTableView.frame, self.messagesTableView.safeAreaInsets).height
Slav

41

Jika Anda perlu menggulir ke akhir konten yang PERSIS, Anda dapat melakukannya seperti ini:

- (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
Bekerja dengan autolayout, TAPI penting untuk memanggil metode ini dari viewDidLayoutSubviews
Omaty

Bisakah Anda menjelaskan mengapa kami perlu melakukan ini yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height; :? Terima kasih.
Unheilig

3
@Unheilig Jika Anda akan menggulir ke self.tableView.contentSize.heightkonten tampilan tabel mungkin tidak terlihat, karena Anda menggulir di bawah konten. Oleh karena itu, Anda harus menggulir ke satu "celah tampilan tabel yang terlihat" di atas akhir tampilan tabel.
Hans One

31

Saya menggunakan autolayout dan tidak ada jawaban yang berhasil untuk saya. Inilah solusi saya yang akhirnya berhasil:

@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
Ini hampir berfungsi untuk saya tetapi saya mendapatkan kesalahan grafis yang aneh saat data tabel saya dimuat dari API eksternal. Dalam kasus saya, apakah saya perlu menelepon setContentOffsetdi beberapa titik lain ketika data telah diambil dan tableview dimuat ulang?
jmoz

Coba atur offset di penangan penyelesaian permintaan Anda.
RaffAl

2
Ini tidak bekerja di ios 10 - hanya menunjukkan tabel dengan latar belakang hitam
RunLoop

2
Alih-alih menggunakan CGFLOAT_MAXsaya digunakan contentSize.height - frame.height + contentInset.bottomsaat mengatur offset konten awal. Menggunakan CGFLOAT_MAXsepertinya mengacaukan saya.
Baza207

23

The diterima solusi dengan @JacobRelkin tidak bekerja untuk saya di iOS 7.0 menggunakan Auto Layout.

Saya memiliki subkelas khusus UIViewControllerdan menambahkan variabel instance _tableViewsebagai subview-nya view. Saya memposisikan _tableViewmenggunakan Tata Letak Otomatis. Saya mencoba memanggil metode ini di akhir viewDidLoaddan bahkan di viewWillAppear:. Tidak ada yang berhasil.

Jadi, saya menambahkan metode berikut ke subkelas khusus saya UIViewController.

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

Menelepon [self tableViewScrollToBottomAnimated:NO]di akhir viewDidLoadkarya. Sayangnya, itu juga menyebabkan tableView:heightForRowAtIndexPath:dipanggil tiga kali untuk setiap sel.


23

Berikut ekstensi yang saya terapkan di Swift 2.0. Fungsi-fungsi ini harus dipanggil setelah tableviewdimuat:

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
Ini lebih baik daripada menggunakan ukuran konten. Untuk Swift3. jika self.numberOfRows (inSection: 0)> 0 {self.scrollToRow (at: IndexPath.init (row: self.numberOfRows (inSection: 0) -1, section: 0), at: .bottom, animated: animated)}
Taman Soohwan

jika scrollToLastRow metode ini tidak bekerja dengan sempurna maka cukup tambahkan self.layoutIfNeeded () dan itu bekerja dengan sempurna !!
Yogesh Patel

17

Sebenarnya cara yang "Lebih Cepat" untuk melakukannya dengan cepat adalah:

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

bekerja Sempurna untuk saya.


15

Detail

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

Kode

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

Pemakaian

tableView.scrollToBottom(animated: true)

Sampel lengkap

Jangan lupa untuk menempelkan kode solusi!

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

9

Saya ingin tabel memuat dengan ujung tabel yang ditunjukkan pada bingkai. Saya menemukan menggunakan

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

tidak berfungsi karena memberikan kesalahan saat tinggi meja kurang dari tinggi bingkai. Perhatikan tabel saya hanya memiliki satu bagian.

Solusi yang berhasil untuk saya adalah menerapkan kode berikut di 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 disetel ke TRUE di viewDidLoad.


Apakah Anda perlu menelepon scrollToRowAtIndexPathsama sekali? Anda sudah menelepon setContentOffsetsetelah itu, yang mungkin membuat panggilan pertama itu tidak berguna.
Carlos P

9

Untuk 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

Untuk 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
Ini tidak menjawab pertanyaan OP, inilah yang berfungsi sejak awal. Anda juga harus memanggil super.viewDidAppear
streem

4

Tentu saja Bug. Mungkin di suatu tempat di kode Anda yang Anda gunakan table.estimatedRowHeight = value(misalnya 100). Gantikan nilai ini dengan nilai tertinggi yang menurut Anda bisa didapat oleh ketinggian baris , misalnya 500 .. Ini akan menyelesaikan masalah dengan kombinasi kode berikut:

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

Setelah banyak mengutak-atik, inilah yang berhasil bagi saya:

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

Kuncinya ternyata tidak membungkus scrollToRowAtIndexPathdi dalam dispatch_asyncseperti yang disarankan beberapa orang, tetapi hanya mengikutinya dengan panggilan kelayoutIfNeeded .

Pemahaman saya tentang ini adalah, memanggil metode gulir di utas saat ini menjamin bahwa offset gulir disetel segera, sebelum tampilan ditampilkan. Ketika saya mengirim ke utas utama, tampilan ditampilkan sesaat sebelum gulungan diterapkan.

(Juga NB Anda memerlukan viewHasAppearedbendera karena Anda tidak ingin goToBottomsetiap kali viewDidLayoutSubviewsdipanggil. Ia dipanggil misalnya setiap kali orientasi berubah.)


3

Menggunakan solusi di atas, ini akan menggulir ke bagian bawah tabel Anda (hanya jika konten tabel dimuat terlebih dahulu):

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

3

Di Swift 3.0

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

2

Jika Anda harus memuat data secara asinkron sebelum menggulir ke bawah, berikut ini solusinya:

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

Fungsi pada swift 3 scroll ke bawah

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

Anda harus menambahkan

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

atau tidak akan bergulir ke bawah.


2

Gunakan kode sederhana ini untuk menggulir tableView ke bawah

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

1
Ini pada dasarnya apa yang dikatakan OP yang sudah dia coba. Jawaban ini tidak menjawab pertanyaan OP tentang bagaimana membuatnya bekerja dengan benar di viewWillAppear.
jk7

2

Terima kasih Jacob atas jawabannya. sangat membantu jika ada yang tertarik dengan monotouch c # version

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

Di iOS, ini berfungsi dengan baik untuk saya

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

Itu perlu dipanggil dari viewDidLayoutSubviews


1

[self.tableViewInfo scrollRectToVisible: CGRectMake (0, self.tableViewInfo.contentSize.height-self.tableViewInfo.height, self.tableViewInfo.width, self.tableViewInfo.height) animasi: YA];


1

Jawaban yang diterima tidak berfungsi dengan tabel saya (ribuan baris, pemuatan dinamis) tetapi kode di bawah berfungsi:

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

Tidak perlu menggulir, Anda dapat melakukannya dengan menggunakan kode ini:

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

1

Jika Anda menyiapkan bingkai untuk tampilan tabel secara terprogram, pastikan Anda menyetel bingkai dengan benar.


1

Di iOS 12, jawaban yang diterima tampaknya rusak. Saya sudah membuatnya bekerja dengan ekstensi berikut



import UIKit

extension UITableView {
    public func scrollToBottom(animated: Bool = true) {
        guard let dataSource = dataSource else {
            return
        }

        let sections = dataSource.numberOfSections?(in: self) ?? 1
        let rows = dataSource.tableView(self, numberOfRowsInSection: sections-1)
        let bottomIndex = IndexPath(item: rows - 1, section: sections - 1)

        scrollToRow(at: bottomIndex,
                    at: .bottom,
                    animated: animated)
    }
}

0

Dalam 3.0 cepat Jika Anda ingin pergi ke Cell of tableview tertentu Ubah nilai indeks sel seperti mengubah nilai "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

Saya yakin solusi lama tidak bekerja dengan swift3.

Jika Anda mengetahui baris nomor dalam tabel, Anda dapat menggunakan:

tableView.scrollToRow(
    at: IndexPath(item: listCountInSection-1, section: sectionCount - 1 ), 
    at: .top, 
    animated: true)
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.