Як прокрутити до нижньої частини UITableView на iPhone, перш ніж відобразиться перегляд


136

У мене є UITableViewте, що заповнене клітинами змінної висоти. Я хотів би, щоб таблиця прокрутилася донизу, коли подання пересунеться у подання.

На даний момент я виконую наступну функцію

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

log - це змінний масив, що містить об'єкти, що складають вміст кожної комірки.

Наведений вище код добре працює, viewDidAppearоднак це має невдалий побічний ефект - відображення верхньої частини таблиці, коли подання вперше з’являється, а потім стрибне вниз. Я вважаю за краще, якби table viewміг прокрутити її донизу, перш ніж вона з’явиться.

Я спробував сувій viewWillAppearі , viewDidLoadале в обох випадках дані не були завантажені в таблицю ще і як виняток.

Будь-які вказівки будуть дуже вдячні, навіть якщо це просто випадок, коли мені можна сказати, що я маю - це все, що можна.

Відповіді:


148

Я вважаю, що дзвонять

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

зробить те, що ти хочеш.


14
Це ідеально дякую. Я створив CGPoint з досить високим значенням Y, завдяки якому він завжди відображатиметься внизу. Після завантаження представлення я можу за допомогою того ж методу (self.table.contentSize.height - self.table.frame.size.height) переміститися до низу тим самим методом
придбання13

8
Хоча це і є ідеальною відповіддю, оскільки нам не потрібно робити розрахунок на кількість комірок, висоту перегляду таблиці тощо. Але я зазначу, що нам потрібно зателефонувати до цього, перш ніж завантажувати перегляд таблиці ... Це не спрацює, якщо ми пишіть це після[table reloadData];
Фахім Паркар

4
не працює на ios 10-12 - таблиця просто зникає вперше
В’ячаслав Герчичов

2
Або просто прокрутіть до CGPoint(x: 0, y: tableView.contentSize.height)?
Amber K

5
Yikes !!! Змушує стіл зникати: -o. Найкраще використовувати [self.table scrollToRowAtIndexPath: indexPath atScrollPosition: UITableViewScrollPositionBottom анімований: 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
Olav Gausaker

3
Осередок вище, ніж UITableView? ніколи не чув такого випадку використання.
Chamira Fernando

@ChamiraFernando це найпростіший спосіб :)
AITAALI_ABDERRAHMANE

Зауважте, що це може мати сенс замінити messages.countна вже реалізовані myTableView.dataSource!.tableView(myTableView, numberOfRowsInSection: 0). Так, це довше, але це може уникнути повторення коду. Вам також потрібно обробити необов'язково dataSource(не змушуйте розгортати, як у цьому зразку).
Микола Суванджієв

@ChamiraFernando Я знаю, що це питання давнє, але тільки тому, що ти ніколи не бачив, це не означає, що цього не відбувається. Щоб відповісти на ваше запитання, у таких програмах, як Foursquare, може виникнути така ситуація, коли користувач пише відгук. Висота комірки більша, ніж висота столового огляду. Це прекрасна ситуація.
Кайо

121

З відповіді Якова це код:

- (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
Слав.

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
Оматій,

Не могли б ви пояснити, чому нам потрібно це робити yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height; ? Дякую.
Unheilig

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в якийсь інший момент, коли дані отримані та перегляд таблиці перезавантажений?
jmoz

Спробуйте встановити компенсацію в оброблювачі завершення вашого запиту.
RaffAl

2
У ios 10 це не працює - просто показує таблицю з чорним фоном
RunLoop

2
Замість використання CGFLOAT_MAXя використовував contentSize.height - frame.height + contentInset.bottomпід час встановлення початкового зміщення вмісту. Використання, CGFLOAT_MAXздавалося, зіпсувало мене.
Baza207

23

Прийняте рішення по @JacobRelkin не працює для мене в прошивкою 7.0 з допомогою Auto Layout.

У мене є спеціальний підклас 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 (за адресою: IndexPath.init (рядок: self.numberOfRows (inSection: 0) -1, розділ: 0), за адресою: .bottom, анімований: анімований)}
Soohwan Park

якщо scrollToLastRow цей метод не працює ідеально, просто додайте self.layoutIfNeeded (), і він ідеально працює !!
Йогеш Патель

16

Деталі

  • Xcode 8.3.2, швидкий 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 InitiLoad встановлений на TRUE у viewDidLoad.


Вам scrollToRowAtIndexPathвзагалі потрібно дзвонити ? Ви вже setContentOffsetдзвоните згодом, що може зробити цей перший дзвінок безглуздим.
Карлос П

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
Це не відповідає на питання ОП, саме це працювало з самого початку. Крім того, ви повинні зателефонувати super.viewDidAppear
струменів

4

Звичайно, Буг. Можливо, десь у коді, який ви використовуєте 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()
}

Ключ виявився не загортанням scrollToRowAtIndexPathвсередину, dispatch_asyncяк пропонують деякі, а просто слідування за ним із закликом доlayoutIfNeeded .

Я розумію це в тому, що виклик методу прокрутки в поточному потоці гарантує, що зміщення прокрутки буде встановлено негайно, перед тим, як буде показано подання. Коли я відправляв головну нитку, подання відображалося на мить до початку прокрутки.

(Крім того, NB вам потрібен 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

Використовуйте цей простий код для прокрутки внизу таблиці

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

1
Це в основному те, що ОП заявило, що вже намагалось. Ця відповідь не стосується питання ОП про те, як змусити її працювати належним чином у viewWillAppear.
jk7

2

Дякую Якову за відповідь. дійсно корисно, якщо хтось цікавий з версією 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];
    }
}

1

Не потрібно ніякої прокрутки, ви можете просто зробити це за допомогою цього коду:

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


0

У Свіфті вам просто потрібно

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

щоб автоматично прокручувати до buttom


0

Швидко 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

Я вважаю, що старі рішення не працюють зі швидким3.

Якщо ви знаєте число рядків у таблиці, ви можете використовувати:

tableView.scrollToRow(
    at: IndexPath(item: listCountInSection-1, section: sectionCount - 1 ), 
    at: .top, 
    animated: true)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.