보기가 나타나기 전에 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가 나타나기 전에 하단으로 스크롤 할 수 .

나는 스크롤을 시도 viewWillAppear하고viewDidLoad 있지만, 두 경우 모두 데이터가 테이블에로드 아직 예외를 던져 모두되지 않았습니다.

내가 가진 것이 가능한 전부라고 말한 경우에도 모든 지침은 대단히 감사하겠습니다.

답변:


148

나는 그 부름을 믿는다

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

당신이 원하는 것을 할 것입니다.


14
정말 고마워요 Y 값이 충분히 높은 CGPoint를 만들어 항상 맨 아래를 표시합니다. 뷰가로드되면 (self.table.contentSize.height-self.table.frame.size.height)를 사용하여 동일한 방법으로 맨 아래로 이동할 수 있습니다
acqu13sce

8
비록 이것이 완벽한 답이지만, 우리는 얼마나 많은 셀, 테이블 뷰의 높이 등을 계산할 필요가 없으므로 테이블 뷰 를 다시로드하기 전에 이것을 호출해야한다고 지적합니다 ... 우리가 작동하지 않으면 작동하지 않습니다 다음에 작성[table reloadData];
Fahim Parkar

4
ios 10-12에서 작동하지 않음-테이블이 처음으로 사라짐
Vyachaslav Gerchicov

2
아니면 그냥 스크롤 CGPoint(x: 0, y: tableView.contentSize.height)?
Amber K

5
이케 !!! 테이블을 사라지게합니다 : -o. [self.table scrollToRowAtIndexPath : indexPath atScrollPosition : UITableViewScrollPositionBottom animated : NO];
칼 하인

122

가장 쉬운 방법은 다음과 같습니다.

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

스위프트 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(이 샘플에서와 같이 강제로 풀지 마십시오).
Nikolay Suvandzhiev

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

왜 이렇게해야하는지 설명해 주 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

2
사용하는 대신 초기 콘텐츠 오프셋을 설정할 때 CGFLOAT_MAX사용했습니다 contentSize.height - frame.height + contentInset.bottom. 를 사용하면 CGFLOAT_MAX나를 망치는 것처럼 보였습니다.
Baza207

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
콘텐츠 크기를 사용하는 것보다 낫습니다. 스위프트 3 if self.numberOfRows (inSection : 0)> 0 {self.scrollToRow (at : IndexPath.init (행 : self.numberOfRows (inSection : 0) -1, 섹션 : 0), at : .bottom, animated : animated)}
박수환

scrollToLastRow이 메소드가 완벽하게 작동하지 않으면 self.layoutIfNeeded ()를 추가하면 완벽하게 작동합니다 !!
Yogesh Patel

16

세부

  • Xcode 8.3.2, 신속한 3.1
  • Xcode 10.2 (10E125), 스위프트 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

실제로 신속하게 수행하는 "Swifter"방법은 다음과 같습니다.

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이후에 이미 전화를 걸면 첫 번째 전화가 무의미해질 수 있습니다.
Carlos P

9

스위프트

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

스위프트 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

물론 버그입니다. 아마도 코드 어딘가에 있습니다 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 .

이것의 나의 이해는의 스크롤 방법 호출이며, 현재 의 스크롤 오프셋 (offset)가 즉시 설정되어 있는지 스레드 보장 하기 전에를 하면 뷰가 표시 됩니다. 메인 스레드로 디스패치 할 때 스크롤이 적용되기 전에 뷰가 즉시 표시되었습니다.

(또한 NB는 당신이 필요 viewHasAppeared당신이 원하지 않기 때문에 플래그를 goToBottom할 때마다 viewDidLayoutSubviews호출된다. 그것은 예를 때마다 방향 변경을 위해 호출됩니다.)


3

위의 솔루션을 사용하면 테이블의 맨 아래로 스크롤됩니다 (테이블 내용이 먼저로드 된 경우에만).

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

3

스위프트 3.0

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

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
이것이 기본적으로 OP가 이미 시도한 내용입니다. 이 답변은 viewWillAppear에서 OP가 제대로 작동하는 방법에 대한 OP의 질문을 다루지 않습니다.
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) animated : YES];


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

1

프로그래밍 방식으로 테이블 뷰의 프레임을 설정하는 경우 프레임을 올바르게 설정하고 있는지 확인하십시오.


0

스위프트에서는 당신이 필요합니다

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

buttom으로 자동 스크롤되도록


0

swift 3.0에서 tableview의 특정 셀로 이동하려는 경우 change "self.yourArr.count"value와 같은 셀 인덱스 값 변경.

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)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.