UITableView가 ReloadData를 완료 한 시점을 알리는 방법?


183

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이 맞다)


이 질문의 가능한 복제본 : stackoverflow.com/questions/4163579/…
pmk

2
내가 본 최고의 솔루션. stackoverflow.com/questions/1483581/…
Khaled Annajar

다음에 대한 나의 대답은, 당신이 도움이 될 stackoverflow.com/questions/4163579/...
Suhas Aithal

여기에 내 대답을 시도 - stackoverflow.com/questions/4163579/...
Suhas Aithal

답변:


288

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

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

[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


3
@rob, 테이블 데이터 소스의 크기에 따라 동일한 실행 루프에서 테이블 뷰의 맨 아래로 이동하는 애니메이션을 만들 수 있습니다. 거대한 테이블로 테스트 코드를 시도하면 다음 실행 루프가 작동 할 때까지 GCD를 사용하여 스크롤을 지연시키는 트릭이 있지만 즉시 스크롤은 실패합니다. 어쨌든이 트릭에 감사드립니다 !!
Mr. T

7
방법 2는 알려지지 않은 이유로 작동하지 않지만 대신 첫 번째 방법을 선택했습니다.
Raj Pawan Gumdal

4
dispatch_async(dispatch_get_main_queue())방법은 작동하지 않을 수 있습니다. 때로는 시스템이 완료 블록 전에, 때로는 렌더링 후 layoutSubviews 및 셀 렌더링을 완료 한 비 결정적 동작을보고 있습니다. 아래에 저에게 효과적 인 답변을 게시 할 것입니다.
Tyler Sheaffer

3
dispatch_async(dispatch_get_main_queue())항상 작동 하는 것은 아닙니다. 여기에 임의의 결과가 표시됩니다.
Vojto

1
메인 스레드는을 실행합니다 NSRunLoop. 실행 루프는 단계가 다르므로 특정 단계 (a를 사용하여 CFRunLoopObserver)에 대한 콜백을 예약 할 수 있습니다 . UIKit은 이벤트 핸들러가 리턴 된 후 나중 단계에서 레이아웃이 발생하도록 스케줄합니다.
rob mayoff

106

빠른:

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

16
이것은 "가까운 미래"에 메인 스레드에 무언가를 파견하는 것과 같습니다. 메인 스레드가 완료 블록을 deque하기 전에 테이블 뷰가 객체를 렌더링하는 것을 볼 수 있습니다. 이런 종류의 해킹은 처음에는 권장되지 않지만 어쨌든 이것을 시도하려면 dispatch_after를 사용해야합니다.
seo

1
Rob의 솔루션은 훌륭하지만 테이블 뷰에 행이 없으면 작동하지 않습니다. Aviel의 솔루션은 테이블에 선이없고 섹션 만있는 경우에도 작동하는 이점이 있습니다.
Chrstph SLN

@Christophe 지금까지 Mock보기 컨트롤러에서 tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int메서드를 재정의하고 재정의에 다시로드가 완료되었음을 알리고 싶은 것을 삽입 하여 행없이 테이블보기에서 Rob의 업데이트를 사용할 수있었습니다 .
Gobe ​​February

49

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 ()의 끝을 결정하는 데에도 사용됩니다.


👍 수동 내에 삽입, 삭제 또는 테이블보기에서 행을 이동처럼, 다시로드 사용자 정의 수행하는 경우 나 또한 작동 beginUpdatesendUpdates전화.
Darrarski

나는 이것이 실제로 현대적인 해결책이라고 믿는다. 실제로는 아이폰 OS, 예제의 일반적인 패턴은 ...입니다 stackoverflow.com/a/47536770/294884
Fattie

나는 이것을 시도했다. 나는 매우 이상한 행동을 얻었다. 내 tableview에는 두 개의 headerView가 올바르게 표시됩니다. setCompletionBlocknumberOfSections쇼 2 안에 ... 지금까지는 너무 좋았습니다. 내 경우 그러나 setCompletionBlock나는 어떻게 tableView.headerView(forSection: 1)그것을 반환 nil! 따라서이 블록은 다시로드하기 전에 발생하거나 이전에 무언가를 포착하거나 잘못하고 있다고 생각합니다. 참고로 Tyler의 답변을 시도했지만 효과가있었습니다! @Fattie

32

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를 추가하고 편집했습니다. 😉 괜찮다 희망
존 보글

2
블록을 호출 한 후에는 반드시 호출 layoutSubviews될 필요는 없지만에 대한 nil후속 호출로 설정되어야합니다 . 강력한 참조가 유지되어 원하는 동작이 아니므로 블록이 실행됩니다. layoutSubviewsreloadData
Mark Bourke

왜 UITableView에 이것을 사용할 수 없습니까? 보이는 인터페이스가 없습니다. 헤더 파일도 가져 왔지만 여전히 동일
Julfikar

2
이 답변의 부록은 기존 콜백이 하나만있는 경우 기존 콜백을 클로버 할 수 있다는 것입니다. 즉, 여러 호출자가 경쟁 조건을 갖습니다. 해결책은 reloadDataCompletionBlock블록 배열 을 만들고 실행시 블록을 반복하고 그 후에 배열을 비우는 것입니다.
Tyler Sheaffer

1) 이것은 Rob의 첫 번째 대답, 즉 layoutIfNeeded를 사용하는 것과 같지 않습니까? 2) 왜 iOS 10을 언급 했습니까? iOS 9에서 작동하지 않습니까?!
Honey

30

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

나는 그의 솔루션 을 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
}

1
좋은! 작은 nit-pick, 당신은 iff nil 💥이라고 호출 하여 if let를 제거 할 수 있습니다reloadDataCompletionBlock?()
Tyler Sheaffer

ios9에 대한 나의 상황에서 이것으로 운이 없다
Matjan

self.reloadDataCompletionBlock? { completion() }되어 있어야합니다self.reloadDataCompletionBlock?()
EMEM

테이블 뷰 높이의 크기 조정을 어떻게 처리합니까? 나는 이전에 tableView.beginUpdates () tableView.layoutIfNeeded () tableView.endUpdates ()를 호출 한
Parth Tamane

10

그리고 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)
}

6

사람들은 여전히이 질문과 답변을 읽고있는 것 같습니다. 그것의 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];

질문을 업데이트했습니다. 내 NSLog가 왜 이렇게 출력되는지 알고 있습니까?
Alan

8
reloadData동 기적이지 않습니다. 예전에는이 답변을 참조하십시오 : stackoverflow.com/a/16071589/193896
bendytree

1
동기식입니다. 샘플 앱으로 테스트하고 보는 것이 매우 쉽습니다. 이 질문에서 @rob의 답변에 연결했습니다. 맨 아래에서 그의 업데이트를 읽으면 그는 이것을 확인했습니다. 시각적 레이아웃 변경에 대해 이야기하고있을 것입니다. tableView가 동 기적으로 눈에 띄게 업데이트되지는 않지만 데이터는 업데이트됩니다. 그렇기 때문에 OP가 필요한 값은 reloadData반환 직후에 정확 합니다.
XJones

1
에서 발생할 것으로 예상되는 내용이 혼동 될 수 있습니다 reloadData. 표시되지 않으면 의미가없는 b / c 행에 viewWillAppear대해 테스트 케이스를 사용하십시오 . 인스턴스에 캐시 된 데이터가 업데이트 되었고 동기화 된 것을 볼 수 있습니다. 레이아웃을 배치 할 때 호출 된 다른 대리자 메소드를 참조하는 경우 가 표시되지 않으면 호출 되지 않습니다. 귀하의 시나리오를 오해하는 경우 설명하십시오. scrollToRowAtIndexPath:tableViewreloadDatatableViewreloadDatatableViewtableViewtableView
XJones

3
재밌는 시간. 2014 년이며 일부 메소드가 동기식인지 비동기식인지에 대한 논쟁이 있습니다. 추측 같은 느낌. 모든 구현 세부 사항은 해당 메소드 이름 뒤에 완전히 불투명합니다. 프로그래밍이 좋지 않습니까?
fatuhoku

5

이 트릭을 사용합니다.이 질문의 복제본에 이미 게시했습니다.

-(void)tableViewDidLoadRows:(UITableView *)tableView{
    // do something after loading, e.g. select a cell.
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // trick to detect when table view has finished loading.
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tableViewDidLoadRows:) object:tableView];
    [self performSelector:@selector(tableViewDidLoadRows:) withObject:tableView afterDelay:0];

    // specific to your controller
    return self.objects.count;
}

@Fattie 긍정적 인 의견이나 부정적인 의견으로 의미하는지 확실하지 않습니다. 그러나 나는 당신이 "이것이 가장 좋은 해결책 인 것 같습니다!"라는 또 다른 대답을 언급 한 것을 보았습니다 . 따라서 상대적으로 말하면이 솔루션이 최고라고 생각하지 않습니다.
Cœur

1
가짜 애니메이션의 부작용에 의존하고 있습니까? 좋은 생각은 아닙니다. 선택기 또는 GCD 수행을 배우고 올바르게 수행하십시오. Btw에는 이제 테이블로드 된 메소드가 있습니다. 다른 방법이 아닌 코드를 호출하는 프레임 워크이기 때문에 문제가없는 개인 프로토콜을 사용하지 않아도되는 경우 사용할 수 있습니다.
malhal

3

실제로 이것은 내 문제를 해결했습니다.

-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

NSSet *visibleSections = [NSSet setWithArray:[[tableView indexPathsForVisibleRows] valueForKey:@"section"]];
if (visibleSections) {
    // hide the activityIndicator/Loader
}}

2

이 방법으로 시도해보십시오.

[tblViewTerms performSelectorOnMainThread:@selector(dataLoadDoneWithLastTermIndex:) withObject:lastTermIndex waitUntilDone:YES];waitUntilDone:YES];

@interface UITableView (TableViewCompletion)

-(void)dataLoadDoneWithLastTermIndex:(NSNumber*)lastTermIndex;

@end

@implementation UITableView(TableViewCompletion)

-(void)dataLoadDoneWithLastTermIndex:(NSNumber*)lastTermIndex
{
    NSLog(@"dataLoadDone");


NSIndexPath* indexPath = [NSIndexPath indexPathForRow: [lastTermIndex integerValue] inSection: 0];

[self selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];

}
@end

테이블이 완전히로드되면 실행합니다

다른 솔루션은 UITableView를 서브 클래스 화 할 수 있다는 것입니다


1

나는 Shawn의 솔루션을 변형하여 사용했습니다.

델리게이트로 사용자 정의 UITableView 클래스를 작성하십시오.

protocol CustomTableViewDelegate {
    func CustomTableViewDidLayoutSubviews()
}

class CustomTableView: UITableView {

    var customDelegate: CustomTableViewDelegate?

    override func layoutSubviews() {
        super.layoutSubviews()
        self.customDelegate?.CustomTableViewDidLayoutSubviews()
    }
}

그런 다음 내 코드에서

class SomeClass: UIViewController, CustomTableViewDelegate {

    @IBOutlet weak var myTableView: CustomTableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.myTableView.customDelegate = self
    }

    func CustomTableViewDidLayoutSubviews() {
        print("didlayoutsubviews")
        // DO other cool things here!!
    }
}

또한 인터페이스 빌더에서 테이블보기를 CustomTableView로 설정했는지 확인하십시오.

여기에 이미지 설명을 입력하십시오


이것은 작동하지만 문제는 전체 테이블보기 다시로드가 아닌 단일 셀을로드 할 때마다 메소드가 적중 되므로이 대답은 분명히 묻는 질문과 관련이 없습니다.
Yash Bedi

사실, 두 번 이상 호출되지만 모든 셀에서 호출되는 것은 아닙니다. 따라서 reloadData를 다시 호출 할 때까지 첫 번째 대의원의 말을 듣고 나머지를 무시할 수 있습니다.
Sam

1

스위프트 3.0 + 우리에 대한 확장을 만들 수 있습니다 UITableViewA를 escaped Closure다음과 같은 :

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

그리고 원하는 곳에서 아래처럼 사용하십시오 :

Your_Table_View.reloadData {
   print("reload done")
 }

이것이 누군가에게 도움이되기를 바랍니다. 건배!


훌륭한 아이디어 .. 유일한 것은 혼란을 피하는 것입니다. reloadData () 대신 함수 이름 t reload를 변경했습니다. 감사합니다
Vijay Kumar AB

1

세부

  • Xcode 버전 10.2.1 (10E1001), 스위프트 5

해결책

import UIKit

// MARK: - UITableView reloading functions

protocol ReloadCompletable: class { func reloadData() }

extension ReloadCompletable {
    func run(transaction closure: (() -> Void)?, completion: (() -> Void)?) {
        guard let closure = closure else { return }
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        closure()
        CATransaction.commit()
    }

    func run(transaction closure: (() -> Void)?, completion: ((Self) -> Void)?) {
        run(transaction: closure) { [weak self] in
            guard let self = self else { return }
            completion?(self)
        }
    }

    func reloadData(completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadData() }, completion: closure)
    }
}

// MARK: - UITableView reloading functions

extension ReloadCompletable where Self: UITableView {
    func reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadRows(at: indexPaths, with: animation) }, completion: closure)
    }

    func reloadSections(_ sections: IndexSet, with animation: UITableView.RowAnimation, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadSections(sections, with: animation) }, completion: closure)
    }
}

// MARK: - UICollectionView reloading functions

extension ReloadCompletable where Self: UICollectionView {

    func reloadSections(_ sections: IndexSet, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadSections(sections) }, completion: closure)
    }

    func reloadItems(at indexPaths: [IndexPath], completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadItems(at: indexPaths) }, completion: closure)
    }
}

용법

UITableView

// Activate
extension UITableView: ReloadCompletable { }

// ......
let tableView = UICollectionView()

// reload data
tableView.reloadData { tableView in print(collectionView) }

// or
tableView.reloadRows(at: indexPathsToReload, with: rowAnimation) { tableView in print(tableView) }

// or
tableView.reloadSections(IndexSet(integer: 0), with: rowAnimation) { _tableView in print(tableView) }

UICollectionView

// Activate
extension UICollectionView: ReloadCompletable { }

// ......
let collectionView = UICollectionView()

// reload data
collectionView.reloadData { collectionView in print(collectionView) }

// or
collectionView.reloadItems(at: indexPathsToReload) { collectionView in print(collectionView) }

// or
collectionView.reloadSections(IndexSet(integer: 0)) { collectionView in print(collectionView) }

전체 샘플

여기에 솔루션 코드추가하는 것을 잊지 마십시오

import UIKit

class ViewController: UIViewController {

    private weak var navigationBar: UINavigationBar?
    private weak var tableView: UITableView?

    override func viewDidLoad() {
        super.viewDidLoad()
        setupNavigationItem()
        setupTableView()
    }
}
// MARK: - Activate UITableView reloadData with completion functions

extension UITableView: ReloadCompletable { }

// MARK: - Setup(init) subviews

extension ViewController {

    private func setupTableView() {
        guard let navigationBar = navigationBar else { return }
        let tableView = UITableView()
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor).isActive = true
        tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        tableView.dataSource = self
        self.tableView = tableView
    }

    private func setupNavigationItem() {
        let navigationBar = UINavigationBar()
        view.addSubview(navigationBar)
        self.navigationBar = navigationBar
        navigationBar.translatesAutoresizingMaskIntoConstraints = false
        navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        navigationBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        let navigationItem = UINavigationItem()
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "all", style: .plain, target: self, action: #selector(reloadAllCellsButtonTouchedUpInside(source:)))
        let buttons: [UIBarButtonItem] = [
                                            .init(title: "row", style: .plain, target: self,
                                                  action: #selector(reloadRowButtonTouchedUpInside(source:))),
                                            .init(title: "section", style: .plain, target: self,
                                                  action: #selector(reloadSectionButtonTouchedUpInside(source:)))
                                            ]
        navigationItem.leftBarButtonItems = buttons
        navigationBar.items = [navigationItem]
    }
}

// MARK: - Buttons actions

extension ViewController {

    @objc func reloadAllCellsButtonTouchedUpInside(source: UIBarButtonItem) {
        let elementsName = "Data"
        print("-- Reloading \(elementsName) started")
        tableView?.reloadData { taleView in
            print("-- Reloading \(elementsName) stopped \(taleView)")
        }
    }

    private var randomRowAnimation: UITableView.RowAnimation {
        return UITableView.RowAnimation(rawValue: (0...6).randomElement() ?? 0) ?? UITableView.RowAnimation.automatic
    }

    @objc func reloadRowButtonTouchedUpInside(source: UIBarButtonItem) {
        guard let tableView = tableView else { return }
        let elementsName = "Rows"
        print("-- Reloading \(elementsName) started")
        let indexPathToReload = tableView.indexPathsForVisibleRows?.randomElement() ?? IndexPath(row: 0, section: 0)
        tableView.reloadRows(at: [indexPathToReload], with: randomRowAnimation) { _tableView in
            //print("-- \(taleView)")
            print("-- Reloading \(elementsName) stopped in \(_tableView)")
        }
    }

    @objc func reloadSectionButtonTouchedUpInside(source: UIBarButtonItem) {
        guard let tableView = tableView else { return }
        let elementsName = "Sections"
        print("-- Reloading \(elementsName) started")
        tableView.reloadSections(IndexSet(integer: 0), with: randomRowAnimation) { _tableView in
            //print("-- \(taleView)")
            print("-- Reloading \(elementsName) stopped in \(_tableView)")
        }
    }
}

extension ViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int { return 1 }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = "\(Date())"
        return cell
    }
}

결과

여기에 이미지 설명을 입력하십시오


0

완료의 아이디어가 전송 될 '마지막 표시'셀이라는 아이디어를 기반으로 다른 접근 방식을 제공하기 위해 cellForRow.

// Will be set when reload is called
var lastIndexPathToDisplay: IndexPath?

typealias ReloadCompletion = ()->Void

var reloadCompletion: ReloadCompletion?

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    // Setup cell

    if indexPath == self.lastIndexPathToDisplay {

        self.lastIndexPathToDisplay = nil

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

    // Return cell
...

func reloadData(completion: @escaping ReloadCompletion) {

    self.reloadCompletion = completion

    self.mainTable.reloadData()

    self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last
}

가능한 한 가지 문제는 다음과 같습니다. 설정 reloadData()전에 완료된 경우 lastIndexPathToDisplay'마지막 표시'셀 lastIndexPathToDisplay이 설정 되기 전에 표시 되고 완료가 호출되지 않습니다 ( '대기'상태에 있음).

self.mainTable.reloadData()

// cellForRowAt could be finished here, before setting `lastIndexPathToDisplay`

self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last

반대로 바꾸면 before로 스크롤하여 완료가 트리거 될 수 reloadData()있습니다.

self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last

// cellForRowAt could trigger the completion by scrolling here since we arm 'lastIndexPathToDisplay' before 'reloadData()'

self.mainTable.reloadData()

0

이 시도:

tableView.backgroundColor = .black

tableView.reloadData()

DispatchQueue.main.async(execute: {

    tableView.backgroundColor = .green

})

reloadData()함수가 완료된 후에 만 tableView 색상이 검은 색에서 녹색으로 변경 됩니다.


0

uitableview의 performBatchUpdates 기능을 사용할 수 있습니다

달성 할 수있는 방법은 다음과 같습니다.

self.tableView.performBatchUpdates({

      //Perform reload
        self.tableView.reloadData()
    }) { (completed) in

        //Reload Completed Use your code here
    }

0

CATransaction의 재사용 가능한 확장 작성 :

public extension CATransaction {
    static func perform(method: () -> Void, completion: @escaping () -> Void) {
        begin()
        setCompletionBlock {
            completion()
        }
        method()
        commit()
    }
}

이제 CATransaction의 확장 메소드를 사용하는 UITableView의 확장을 작성하십시오.

public extension UITableView {
    func reloadData(completion: @escaping (() -> Void)) {
       CATransaction.perform(method: {
           reloadData()
       }, completion: completion)
    }
}

용법:

tableView.reloadData(completion: {
    //Do the stuff
})

-2

데이터를 다시로드 한 후 무언가를 위해 사용할 수 있습니다.

[UIView animateWithDuration:0 animations:^{
    [self.contentTableView reloadData];
} completion:^(BOOL finished) {
    _isUnderwritingUpdate = NO;
}];

-20

지연 설정을 시도하십시오.

[_tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.2];
[_activityIndicator performSelector:@selector(stopAnimating) withObject:nil afterDelay:0.2];

14
이것은 위험합니다. 지연 시간보다 재장 전하는 데 시간이 더 걸리면 어떻게 되나요?
rob
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.