[objective-c] UITableView가 ReloadData를 완료 한 시점을 알리는 방법?

UITableView가 수행 된 후 맨 아래로 스크롤하려고합니다. [self.tableView reloadData]

원래는

 [self.tableView reloadData]
 NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];

[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

그러나 나는 스크롤이 이후 발생하지 않도록 reloadData는, 비동기 것을 읽고 self.tableView, [self.tableView numberOfSections]그리고 [self.tableView numberOfRowsinSection모두 0이다.

감사!

이상한 점은 내가 사용하고 있다는 것입니다.

[self.tableView reloadData];
NSLog(@"Number of Sections %d", [self.tableView numberOfSections]);
NSLog(@"Number of Rows %d", [self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1);

콘솔에서 Sections = 1, Row = -1을 반환합니다.

정확히 동일한 NSLog를 수행하면 cellForRowAtIndexPathSections = 1 및 Row = 8이 표시됩니다. (8이 맞다)



답변

재로드는 다음 레이아웃 패스 중에 발생하며 일반적으로 제어를 실행 루프로 되돌릴 때 발생합니다 (예를 들어, 단추 동작 또는 모든 반환).

따라서 테이블 뷰를 다시로드 한 후 무언가를 실행하는 한 가지 방법은 테이블 뷰가 즉시 레이아웃을 수행하도록하는 것입니다.

[self.tableView reloadData];
[self.tableView layoutIfNeeded];
 NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

또 다른 방법은 후 레이아웃 코드가 다음을 사용하여 나중에 실행되도록 예약하는 것입니다 dispatch_async.

[self.tableView reloadData];

dispatch_async(dispatch_get_main_queue(), ^{
     NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection:([self.tableView numberOfSections]-1)];

    [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});

최신 정보

추가 조사 후, 나는 테이블 뷰 보내는 것을 발견 tableView:numberOfSections:하고 tableView:numberOfRowsInSection:부터 반환하기 전에 데이터 소스에 reloadData. 대리자가을 구현 tableView:heightForRowAtIndexPath:하면 테이블 뷰는에서 반환하기 전에 (각 행에 대해)도 보냅니다 reloadData.

그러나 테이블보기는 tableView:cellForRowAtIndexPath:또는 tableView:headerViewForSection레이아웃 단계를 보내지 않습니다.이 단계는 제어를 실행 루프로 되돌릴 때 기본적으로 발생합니다.

또한 작은 테스트 프로그램에서 질문의 코드가 (보기 또는 보내기와 같은) 특별한 작업을 수행 하지 않고 테이블보기의 맨 아래로 올바르게 스크롤됩니다 .layoutIfNeededdispatch_async


답변

빠른:

extension UITableView {
    func reloadData(completion:@escaping ()->()) {
        UIView.animateWithDuration(0, animations: { self.reloadData() })
            { _ in completion() }
    }
}

...somewhere later...

tableView.reloadData {
    println("done")
}

목표 -C :

[UIView animateWithDuration:0 animations:^{
    [myTableView reloadData];
} completion:^(BOOL finished) {
    //Do something after that...
}];


답변

Xcode 8.2.1, iOS 10 및 swift 3부터

tableView.reloadData()CATransaction 블록을 사용하여 쉽게 끝을 결정할 수 있습니다 .

CATransaction.begin()
CATransaction.setCompletionBlock({
    print("reload completed")
    //Your completion code here
})
print("reloading")
tableView.reloadData()
CATransaction.commit()

위의 내용은 UICollectionView의 reloadData () 및 UIPickerView의 reloadAllComponents ()의 끝을 결정하는 데에도 사용됩니다.


답변

dispatch_async(dispatch_get_main_queue())위 의 방법은 작동하지 않을 수 있습니다 . 때로는 시스템이 완료 블록 전에, 때로는 렌더링 후 layoutSubviews 및 셀 렌더링을 완료 한 비 결정적 동작을보고 있습니다.

다음은 iOS 10에서 100 % 작동하는 솔루션입니다. UITableView 또는 UICollectionView를 사용자 정의 하위 클래스로 인스턴스화하는 기능이 필요합니다. UICollectionView 솔루션은 다음과 같지만 UITableView와 동일합니다.

CustomCollectionView.h :

#import <UIKit/UIKit.h>

@interface CustomCollectionView: UICollectionView

- (void)reloadDataWithCompletion:(void (^)(void))completionBlock;

@end

CustomCollectionView.m :

#import "CustomCollectionView.h"

@interface CustomCollectionView ()

@property (nonatomic, copy) void (^reloadDataCompletionBlock)(void);

@end

@implementation CustomCollectionView

- (void)reloadDataWithCompletion:(void (^)(void))completionBlock
{
    self.reloadDataCompletionBlock = completionBlock;
    [self reloadData];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (self.reloadDataCompletionBlock) {
        self.reloadDataCompletionBlock();
        self.reloadDataCompletionBlock = nil;
    }
}

@end

사용법 예 :

[self.collectionView reloadDataWithCompletion:^{
    // reloadData is guaranteed to have completed
}];

이 답변의 스위프트 버전은 여기 를 참조 하십시오.


답변

타일러 시퍼와 같은 문제가있었습니다.

나는 그의 솔루션 을 Swift에서 구현 했으며 내 문제를 해결했습니다.

스위프트 3.0 :

final class UITableViewWithReloadCompletion: UITableView {
  private var reloadDataCompletionBlock: (() -> Void)?

  override func layoutSubviews() {
    super.layoutSubviews()

    reloadDataCompletionBlock?()
    reloadDataCompletionBlock = nil
  }


  func reloadDataWithCompletion(completion: @escaping () -> Void) {
    reloadDataCompletionBlock = completion
    self.reloadData()
  }
}

스위프트 2 :

class UITableViewWithReloadCompletion: UITableView {

  var reloadDataCompletionBlock: (() -> Void)?

  override func layoutSubviews() {
    super.layoutSubviews()

    self.reloadDataCompletionBlock?()
    self.reloadDataCompletionBlock = nil
  }

  func reloadDataWithCompletion(completion:() -> Void) {
      reloadDataCompletionBlock = completion
      self.reloadData()
  }
}

사용법 예 :

tableView.reloadDataWithCompletion() {
 // reloadData is guaranteed to have completed
}


답변

그리고 UICollectionViewkolaworld의 답변을 기반으로 한 버전 :

https://stackoverflow.com/a/43162226/1452758

테스트가 필요합니다. iOS 9.2, Xcode 9.2 베타 2에서 지금까지 작동하며 collectionView를 인덱스로 스크롤하여 클로저로 사용합니다.

extension UICollectionView
{
    /// Calls reloadsData() on self, and ensures that the given closure is
    /// called after reloadData() has been completed.
    ///
    /// Discussion: reloadData() appears to be asynchronous. i.e. the
    /// reloading actually happens during the next layout pass. So, doing
    /// things like scrolling the collectionView immediately after a
    /// call to reloadData() can cause trouble.
    ///
    /// This method uses CATransaction to schedule the closure.

    func reloadDataThenPerform(_ closure: @escaping (() -> Void))
    {
        CATransaction.begin()
            CATransaction.setCompletionBlock(closure)
            self.reloadData()
        CATransaction.commit()
    }
}

용법:

myCollectionView.reloadDataThenPerform {
    myCollectionView.scrollToItem(at: indexPath,
            at: .centeredVertically,
            animated: true)
}


답변

사람들은 여전히이 질문과 답변을 읽고있는 것 같습니다. 그것의 B / C, 나는 실제로 이것과 관련이없는 Synchronous 라는 단어를 제거하기 위해 대답을 편집하고 있습니다.

When [tableView reloadData]반환하면 tableView 뒤의 내부 데이터 구조가 업데이트되었습니다. 따라서 방법이 완료되면 하단으로 안전하게 스크롤 할 수 있습니다. 내 앱에서 이것을 확인했습니다. @ rob-mayoff가 널리 받아 들인 대답은 용어를 혼동하지만 마지막 업데이트에서도 동일하게 인정합니다.

당신이 경우 tableView하단으로 스크롤되지 당신은 당신이 게시하지 않은 다른 코드에 문제가있을 수 있습니다. 스크롤이 완료된 후 데이터를 변경 중이거나 맨 아래로 다시로드하거나 스크롤하지 않는 것일 수 있습니다.

다음과 같이 로깅을 추가하여 이후 테이블 데이터가 올바른지 확인하십시오 reloadData. 샘플 앱에 다음 코드가 있으며 완벽하게 작동합니다.

// change the data source

NSLog(@"Before reload / sections = %d, last row = %d",
      [self.tableView numberOfSections],
      [self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]);

[self.tableView reloadData];

NSLog(@"After reload / sections = %d, last row = %d",
      [self.tableView numberOfSections],
      [self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]);

[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]-1
                                                          inSection:[self.tableView numberOfSections] - 1]
                      atScrollPosition:UITableViewScrollPositionBottom
                              animated:YES];