So scrollen Sie auf dem iPhone zum Ende einer UITableView, bevor die Ansicht angezeigt wird

136

Ich habe ein UITableView , die mit Zellen variabler Höhe gefüllt ist. Ich möchte, dass die Tabelle nach unten rollt, wenn die Ansicht in die Ansicht verschoben wird.

Ich habe derzeit die folgende Funktion

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

log ist ein veränderbares Array, das die Objekte enthält, aus denen der Inhalt jeder Zelle besteht.

Der obige Code funktioniert einwandfrei, viewDidAppearhat jedoch den unglücklichen Nebeneffekt, dass der obere Rand der Tabelle angezeigt wird, wenn die Ansicht zum ersten Mal angezeigt wird, und dann nach unten gesprungen wird. Ich würde es vorziehen, wenn dietable view nach unten gescrollt werden könnte, bevor es erscheint.

Ich habe das Scrollen versucht viewWillAppearund viewDidLoadin beiden Fällen wurden die Daten noch nicht in die Tabelle geladen und beide lösen eine Ausnahme aus.

Jede Anleitung wäre sehr dankbar, auch wenn es nur darum geht, mir zu sagen, was ich habe, ist alles, was möglich ist.

acc13sce
quelle

Antworten:

148

Ich glaube an diese Berufung

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

wird tun was du willst.

Jacob Relkin
quelle
14
Das ist perfekt, danke. Ich habe einen CGPoint mit einem ausreichend hohen Y-Wert erstellt, sodass immer der untere Wert angezeigt wird. Sobald die Ansicht geladen wurde, kann ich (self.table.contentSize.height - self.table.frame.size.height) verwenden, um mit derselben Methode nach unten zu gelangen
Acqu13sce
8
Dies ist zwar die perfekte Antwort, da wir nicht berechnen müssen, wie viele Zellen, Höhe der Tabellenansicht usw., ABER ich würde darauf hinweisen, dass wir dies aufrufen müssen, bevor wir die Tabellenansicht neu laden ... Es wird nicht funktionieren, wenn wir Schreiben Sie dies nach[table reloadData];
Fahim Parkar
4
funktioniert nicht auf ios 10-12 - Tabelle verschwinden einfach zum ersten Mal
Vyachaslav Gerchicov
2
Oder einfach blättern zu CGPoint(x: 0, y: tableView.contentSize.height)?
Amber K
5
Huch !!! Lässt den Tisch verschwinden: -o. Am besten mit [self.table scrollToRowAtIndexPath: indexPath atScrollPosition: UITableViewScrollPositionBottom animiert: NO];
Carl Hine
122

Ich denke, der einfachste Weg ist folgender:

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

Swift 3 Version:

if messages.count > 0 {
    userDefinedOptionsTableView.scrollToRow(at: IndexPath(item:messages.count-1, section: 0), at: .bottom, animated: true)
}
Chamira Fernando
quelle
3
Es funktioniert nicht, wenn die Zelle größer ist als die UITableView
Olav Gausaker
3
Zelle ist größer als UITableView? habe noch nie einen solchen Anwendungsfall gehört.
Chamira Fernando
@ChamiraFernando das ist der einfachste Weg :)
AITAALI_ABDERRAHMANE
Beachten Sie, dass es möglicherweise sinnvoll ist, das messages.countdurch das bereits implementierte zu ersetzen myTableView.dataSource!.tableView(myTableView, numberOfRowsInSection: 0). Ja, es ist länger, aber es kann eine Codewiederholung vermeiden. Sie müssen auch die Option behandeln dataSource(nicht wie in diesem Beispiel das Auspacken erzwingen).
Nikolay Suvandzhiev
@ChamiraFernando Ich weiß, dass diese Frage alt ist, aber nur weil du sie nie gesehen hast, heißt das nicht, dass es nicht passiert. Um Ihre Frage zu beantworten, können Apps wie Foursquare diese Situation haben, in der Benutzer eine Bewertung schreiben. Die Höhe der Zelle ist größer als die Höhe der Tabellenansicht. Es ist eine vollkommen gute Situation.
Caio
121

Nach Jacobs Antwort ist dies der Code:

- (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];
    }
}
Osama F Elias
quelle
Unter iOS 11 sollten Sie die angepasste Rahmenhöhe für die Tabellenansicht verwenden:UIEdgeInsetsInsetRect(self.messagesTableView.frame, self.messagesTableView.safeAreaInsets).height
Slav
41

Wenn Sie zum GENAUEN Ende des Inhalts scrollen müssen, können Sie dies folgendermaßen tun:

- (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];
}
Hans Eins
quelle
7
Funktioniert mit Autolayout, ABER es ist wichtig, diese Methode von viewDidLayoutSubviews
Omaty
Könnten Sie bitte erklären, warum wir dies tun müssen : yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height; ? Vielen Dank.
Unheilig
3
@Unheilig Wenn Sie zum self.tableView.contentSize.heightInhalt der Tabellenansicht scrollen möchten, ist dieser möglicherweise nicht sichtbar, da Sie unter den Inhalt scrollen. Daher müssen Sie zu einer "sichtbaren Lücke in der Tabellenansicht" über dem Ende der Tabellenansicht scrollen.
Hans One
31

Ich verwende Autolayout und keine der Antworten hat bei mir funktioniert. Hier ist meine Lösung, die endlich funktioniert hat:

@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)];
    }
}
RaffAl
quelle
1
Das funktioniert fast für mich, aber ich bekomme einen seltsamen Grafikfehler, während meine Tabellendaten von einer externen API geladen werden. In meinem Fall muss ich anrufensetContentOffset an einem anderen Punkt , wenn die Daten abgerufen und die Tabellenansicht neu geladen wurden?
jmoz
Versuchen Sie, den Offset in einem Completion-Handler Ihrer Anfrage festzulegen.
RaffAl
2
Dies funktioniert nicht in iOS 10 - zeigt einfach eine Tabelle mit einem schwarzen Hintergrund
RunLoop
2
Anstelle von CGFLOAT_MAXI habe ich contentSize.height - frame.height + contentInset.bottombeim Einstellen des anfänglichen Inhaltsversatzes verwendet. Verwenden CGFLOAT_MAXschien für mich durcheinander zu bringen.
Baza207
23

Die von @JacobRelkin akzeptierte Lösung funktionierte in iOS 7.0 mit Auto Layout nicht für mich.

Ich habe eine benutzerdefinierte Unterklasse von UIViewControllerund fügte eine Instanzvariable _tableViewals Unteransicht ihrer hinzu view. Ich habe _tableViewmit Auto Layout positioniert . Ich habe versucht, diese Methode am Ende viewDidLoadund sogar in aufzurufen viewWillAppear:. Beides hat nicht funktioniert.

Also habe ich meiner benutzerdefinierten Unterklasse von die folgende Methode hinzugefügt UIViewController.

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

Berufung [self tableViewScrollToBottomAnimated:NO] am Ende der viewDidLoadArbeiten. Leider wird es auch tableView:heightForRowAtIndexPath:dreimal für jede Zelle aufgerufen.

ma11hew28
quelle
23

Hier ist eine Erweiterung, die ich in Swift 2.0 implementiert habe. Diese Funktionen sollten nach dem tableviewLaden von aufgerufen werden :

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)
        }
    }
}
Ryan Herubin
quelle
3
Dies ist besser als die Verwendung der Inhaltsgröße. Für Swift3. if self.numberOfRows (inSection: 0)> 0 {self.scrollToRow (at: IndexPath.init (Zeile: self.numberOfRows (inSection: 0) -1, section: 0), at: .bottom, animiert: animiert)}
Soohwan Park
Wenn scrollToLastRow diese Methode nicht perfekt funktioniert, fügen Sie einfach self.layoutIfNeeded () hinzu und es funktioniert perfekt !!
Yogesh Patel
16

Einzelheiten

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

Code

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

Verwendung

tableView.scrollToBottom(animated: true)

Vollständige Probe

Vergessen Sie nicht, den Lösungscode einzufügen!

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
    }
}
Wassili Bodnarchuk
quelle
15

Tatsächlich ist ein "schneller" Weg, dies schnell zu tun ,:

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

Arbeit Perfekt für mich.

XcodeNOOB
quelle
9

Ich wollte, dass die Tabelle mit dem Ende der im Rahmen gezeigten Tabelle geladen wird. Ich fand mit

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

funktionierte nicht, da es einen Fehler gab, wenn die Tischhöhe kleiner als die Rahmenhöhe war. Beachten Sie, dass meine Tabelle nur einen Abschnitt enthält.

Die Lösung, die für mich funktioniert hat, war die Implementierung des folgenden Codes in 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];
    }
}

Das BOOL ivar initialLoad wird in viewDidLoad auf TRUE gesetzt.

TJ
quelle
Müssen Sie überhaupt anrufen scrollToRowAtIndexPath? Sie rufen bereits setContentOffsetdanach an, was diesen ersten Anruf möglicherweise sinnlos macht.
Carlos P
9

Für 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)
}
Segev
quelle
5

Sie sollten UITableViewScrollPositionBottomstattdessen verwenden.

Erphan Rajput
quelle
5

Für 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)
}
Irshad Qureshi
quelle
3
Dies beantwortet keine OP-Frage, das hat von Anfang an funktioniert. Auch sollten Sie super.viewDidAppear
Streem
4

Ist natürlich ein Bug. Wahrscheinlich irgendwo in Ihrem Code, den Sie verwenden table.estimatedRowHeight = value(zum Beispiel 100). Ersetzen Sie diesen Wert durch den höchsten Wert, von dem Sie glauben, dass eine Zeilenhöhe erreicht werden könnte , z. B. 500. Dies sollte das Problem in Kombination mit dem folgenden Code lösen:

//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)
})
Mohsen Karbassi
quelle
4

Nach vielem Fummeln hat das für mich funktioniert:

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

Der Schlüssel erwies sich als nicht Einwickeln scrollToRowAtIndexPathinnerhalb von dispatch_asyncwie einige vorgeschlagen haben, sondern einfach mit einem Anruf folgende eslayoutIfNeeded .

Mein Verständnis davon ist, dass das Aufrufen der Bildlaufmethode im aktuellen Thread garantiert, dass der Bildlaufversatz unmittelbar vorher festgelegt wird die Ansicht angezeigt wird. Als ich zum Haupt-Thread schickte, wurde die Ansicht für einen Moment angezeigt, bevor der Bildlauf wirksam wurde.

(Auch NB Sie brauchen die viewHasAppearedFlagge, weil Sie nicht wollengoToBottom jedes MalviewDidLayoutSubviews . Sie wird zum Beispiel aufgerufen, wenn sich die Ausrichtung ändert.)

skot
quelle
3

Mit den oben genannten Lösungen wird zum Ende Ihrer Tabelle gescrollt (nur wenn der Tabelleninhalt zuerst geladen wird):

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

In Swift 3.0

self.tableViewFeeds.setContentOffset(CGPoint(x: 0, y: CGFLOAT_MAX), animated: true)
Amit Verma
quelle
2

Wenn Sie die Daten vor dem Scrollen asynchron laden müssen, ist hier die mögliche Lösung:

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
            }
        }
    }
}
SoftDesigner
quelle
2

Funktion bei Swift 3 nach unten scrollen

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

Sie sollten hinzufügen

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

oder es wird nicht nach unten scrollen.


quelle
2

Verwenden Sie diesen einfachen Code, um die Tabellenansicht nach unten zu scrollen

NSInteger rows = [tableName numberOfRowsInSection:0];
if(rows > 0) {
    [tableName scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:rows-1 inSection:0]
                     atScrollPosition:UITableViewScrollPositionBottom
                             animated:YES];
}
Bibin Joseph
quelle
1
Dies ist im Grunde, was der OP sagte, er habe es bereits versucht. Diese Antwort geht nicht auf die Frage des OP ein, wie es in viewWillAppear ordnungsgemäß funktioniert.
jk7
2

Danke Jacob für die Antwort. wirklich hilfreich, wenn jemand mit monotouch c # version interessant ist

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 );
    }
}
Mahesh
quelle
1

In iOS hat das bei mir gut funktioniert

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

Es muss von aufgerufen werden viewDidLayoutSubviews

Josip B.
quelle
1

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

Leetvin
quelle
1

Die akzeptierte Antwort funktionierte nicht mit meiner Tabelle (Tausende von Zeilen, dynamisches Laden), aber der folgende Code funktioniert:

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

Sie müssen nicht scrollen. Sie können dies einfach mit diesem Code tun:

[YOURTABLEVIEWNAME setContentOffset:CGPointMake(0, CGFLOAT_MAX)];
Anuj Kumar Rai
quelle
1

Wenn Sie den Rahmen für die Tabellenansicht programmgesteuert einrichten, stellen Sie sicher, dass Sie den Rahmen korrekt einstellen.

Narasimha Nallamsetty
quelle
0

In Swift brauchen Sie nur

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

um es automatisch zum Buttom zu scrollen

Sogar Cheng
quelle
0

In Swift 3.0 Wenn Sie zu einer bestimmten Zelle der Tabellenansicht wechseln möchten, ändern Sie den Zellenindexwert wie den Wert "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)
    }
}
Amit Verma
quelle
0

Ich glaube, alte Lösungen funktionieren nicht mit swift3.

Wenn Sie Zahlenzeilen in der Tabelle kennen, können Sie Folgendes verwenden:

tableView.scrollToRow(
    at: IndexPath(item: listCountInSection-1, section: sectionCount - 1 ), 
    at: .top, 
    animated: true)
ymutlu
quelle