UICollectionView가 완전히로드되었는지 어떻게 알 수 있습니까?


98

UICollectionView가 완전히로드 될 때마다 몇 가지 작업을 수행해야합니다. 즉, 이때 모든 UICollectionView의 데이터 소스 / 레이아웃 메서드를 호출해야합니다. 그것을 어떻게 압니까 ?? UICollectionView로드 상태를 알 수있는 대리자 메서드가 있습니까?

답변:


62
// In viewDidLoad
[self.collectionView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld context:NULL];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary  *)change context:(void *)context
{
    // You will get here when the reloadData finished 
}

- (void)dealloc
{
    [self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];
}

1
뒤로 이동할 때이 문제가 발생하고 그렇지 않으면 잘 작동합니다.
ericosg 2015-04-28

4
@ericosg도 추가 [self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];합니다 -viewWillDisappear:animated:.
pxpgraphics

훌륭한! 대단히 감사합니다!
Abdo

41
스크롤 할 때도 콘텐츠 크기가 변경되므로 신뢰할 수 없습니다.
Ryan Heitner 2015 년

비동기식 휴식 방법을 사용하여 데이터를 가져 오는 컬렉션 뷰를 시도했습니다. 즉,이 메서드가 실행될 때 Visible Cells는 항상 비어 있습니다. 즉, 시작시 컬렉션의 항목으로 스크롤합니다.
처키

155

이것은 나를 위해 일했습니다.

[self.collectionView reloadData];
[self.collectionView performBatchUpdates:^{}
                              completion:^(BOOL finished) {
                                  /// collection-view finished reload
                              }];

Swift 4 구문 :

self.collectionView.reloadData()
self.collectionView.performBatchUpdates(nil, completion: {
    (result) in
    // ready
})

1
아이폰 OS 9에서 나를 위해 일한
Magoo

5
@ G.Abhisek Errrr, 물론입니다 .... 무엇을 설명해야합니까? 첫 번째 줄 reloadData은를 새로 collectionView고쳐서 데이터 소스 메서드를 다시 그립니다 ... performBatchUpdates에 완료 블록이 연결되어 있으므로 둘 다 메인 스레드에서 수행되는 경우 그 자리에 배치 된 코드가 /// collection-view finished reload새로 배치 된 상태로 실행 된다는 것을 알고 있습니다.collectionView
Magoo

8
collectionView 동적 크기의 세포했을 때 나를 위해 일하지 않는다
ingaham을

2
이 방법은 두통없이 완벽한 솔루션입니다.
arjavlad

1
@ingaham 이것은 동적 크기의 셀로 완벽하게 작동합니다. Swift 4.2 IOS 12
Romulo BM

27

실제로는 매우 간단합니다.

예를 들어 UICollectionView의 reloadData 메서드를 호출하거나 레이아웃의 invalidateLayout 메서드를 호출 할 때 다음을 수행합니다.

dispatch_async(dispatch_get_main_queue(), ^{
    [self.collectionView reloadData];
});

dispatch_async(dispatch_get_main_queue(), ^{
    //your stuff happens here
    //after the reloadData/invalidateLayout finishes executing
});

이것이 작동하는 이유 :

기본 스레드 (모든 UI 업데이트를 수행해야하는 곳)에는 본질적으로 직렬 인 기본 대기열이 있습니다. 즉, FIFO 방식으로 작동합니다. 따라서 위의 예에서 첫 번째 블록이 호출 reloadData되고 메서드가 호출되고 두 번째 블록의 다른 항목이 이어집니다.

이제 주 스레드도 차단됩니다. 따라서 reloadData실행 하는 데 3 초가 걸리면 두 번째 블록의 처리가 해당 3 초에 의해 지연됩니다.


2
나는 실제로 이것을 테스트했습니다. 블록은 다른 모든 위임 메서드보다 먼저 호출되고 있습니다. 그래서 작동하지 않습니까?
taylorcressy 2014-08-05

@taylorcressy 안녕하세요, 저는 현재 2 개의 프로덕션 앱에서 사용하고있는 코드를 업데이트했습니다. 설명도 포함되어 있습니다.
dezinezync 2014 년

나를 위해 작동하지 않습니다. reloadData를 호출하면 collectionView는 두 번째 블록 실행 후 셀을 요청합니다. 그래서 @taylorcressy와 같은 문제가 있습니다.
Raphael

1
두 번째 블록 호출을 첫 번째 블록에 넣어 볼 수 있습니까? 확인되지 않았지만 작동 할 수 있습니다.
dezinezync 2014 년

6
reloadData이제 메인 스레드에서 셀로드를 대기열에 추가합니다 (메인 스레드에서 호출 된 경우에도). 따라서 셀은 반환 cellForRowAtIndexPath:후 실제로로드 되지 않습니다 (호출되지 않음) reloadData. 셀이로드 된 후 실행하려는 코드를 래핑 dispatch_async(dispatch_get_main...하고를 호출 한 후에이를 호출 reloadData하면 원하는 결과를 얻을 수 있습니다.
Tylerc230

12

훌륭한 @dezinezync 답변에 추가하려면 다음을 수행하십시오.

Swift 3+

collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
    // your stuff here executing after collectionView has been layouted
}

@nikas 우리가 추가해야 할 뷰가 나타났습니다!
SARATH SASI

1
반드시 그런 것은 아니지만 레이아웃이 마지막으로로드 될 때 원하는 작업을 어디에서나 호출 할 수 있습니다.
nikans

항목을 삽입 한 후 마지막에 collectionView 스크롤을 만들려고 스크롤 깜박임이 발생했습니다.이 문제가 해결되었습니다. 감사합니다.
Skoua

6

다음과 같이하십시오.

       UIView.animateWithDuration(0.0, animations: { [weak self] in
                guard let strongSelf = self else { return }

                strongSelf.collectionView.reloadData()

            }, completion: { [weak self] (finished) in
                guard let strongSelf = self else { return }

                // Do whatever is needed, reload is finished here
                // e.g. scrollToItemAtIndexPath
                let newIndexPath = NSIndexPath(forItem: 1, inSection: 0)
                strongSelf.collectionView.scrollToItemAtIndexPath(newIndexPath, atScrollPosition: UICollectionViewScrollPosition.Left, animated: false)
        })

4

reloadData () 호출 직후 layoutIfNeeded ()를 통해 동기식 레이아웃 전달을 시도하십시오. iOS 12에서 UICollectionView 및 UITableView 모두에서 작동하는 것 같습니다.

collectionView.reloadData()
collectionView.layoutIfNeeded() 

// cellForItem/sizeForItem calls should be complete
completion?()

감사합니다! 나는 그 문제의 해결책을 꽤 오랫동안 찾고있었습니다.
Trzy Gracje 19

3

dezinezync 가 대답 했듯이 , 필요한 것은 reloadDataa UITableView또는 에서 코드 블록을 메인 큐에 디스패치하는 UICollectionView것입니다. 그러면이 블록은 셀 대기열에서 제외 된 후에 실행됩니다.

사용할 때 이것을 더 똑바로 만들기 위해 다음과 같은 확장을 사용합니다.

extension UICollectionView {
    func reloadData(_ completion: @escaping () -> Void) {
        reloadData()
        DispatchQueue.main.async { completion() }
    }
}

그것은 또한에 구현 될 수 UITableView뿐만 아니라


3

RxSwift / RxCocoa를 사용한 다른 접근 방식 :

        collectionView.rx.observe(CGSize.self, "contentSize")
            .subscribe(onNext: { size in
                print(size as Any)
            })
            .disposed(by: disposeBag)

1
나는 이것을 좋아한다. performBatchUpdates제 경우에는 작동하지 않았습니다. 건배!
Zoltán

@ MichałZiobro, 어딘가에서 코드를 업데이트 할 수 있습니까? 이 메서드는 contentSize가 변경된 경우에만 호출해야합니까? 따라서 모든 스크롤에서 변경하지 않으면 작동합니다!
Chinh Nguyen

1

이것은 나를 위해 작동합니다.

__weak typeof(self) wself= self;
[self.contentCollectionView performBatchUpdates:^{
    [wself.contentCollectionView reloadData];
} completion:^(BOOL finished) {
    [wself pageViewCurrentIndexDidChanged:self.contentCollectionView];
}];

2
말도 안돼 ... 이건 어떤 식 으로든 레이아웃이 끝나지 않았습니다.
Magoo

1

컬렉션 뷰가 사용자에게 표시되기 전에로드 될 때 표시되는 모든 셀에 대해 수행해야하는 작업이 필요했습니다.

public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    if shouldPerformBatch {
        self.collectionView.performBatchUpdates(nil) { completed in
            self.modifyVisibleCells()
        }
    }
}

컬렉션 뷰를 스크롤 할 때 호출되므로이 오버 헤드를 방지하기 위해 다음을 추가했습니다.

private var souldPerformAction: Bool = true

그리고 행동 자체에서 :

private func modifyVisibleCells() {
    if self.shouldPerformAction {
        // perform action
        ...
        ...
    }
    self.shouldPerformAction = false
}

작업은 초기 상태에서 보이는 셀의 수로 여러 번 수행됩니다. 그러나 이러한 모든 호출에서 동일한 수의 보이는 셀 (모두)을 갖게됩니다. 그리고 부울 플래그는 사용자가 컬렉션 뷰와 상호 작용하기 시작한 후에 다시 실행되지 않도록합니다.


1

컬렉션 뷰를 다시로드 한 후 다음 작업을 수행했습니다. API 응답에서도이 코드를 사용할 수 있습니다.

self.collectionView.reloadData()

DispatchQueue.main.async {
   // Do Task after collection view is reloaded                
}

1

지금까지 찾은 최고의 솔루션은 CATransaction완성을 처리하기 위해 사용하는 것 입니다.

스위프트 5 :

CATransaction.begin()
CATransaction.setCompletionBlock {
    // UICollectionView is ready
}

collectionView.reloadData()

CATransaction.commit()

0

Def 이렇게 :

//Subclass UICollectionView
class MyCollectionView: UICollectionView {

    //Store a completion block as a property
    var completion: (() -> Void)?

    //Make a custom funciton to reload data with a completion handle
    func reloadData(completion: @escaping() -> Void) {
        //Set the completion handle to the stored property
        self.completion = completion
        //Call super
        super.reloadData()
    }

    //Override layoutSubviews
    override func layoutSubviews() {
        //Call super
        super.layoutSubviews()
        //Call the completion
        self.completion?()
        //Set the completion to nil so it is reset and doesn't keep gettign called
        self.completion = nil
    }

}

그런 다음 VC 내부에서 이렇게 호출하십시오.

let collection = MyCollectionView()

self.collection.reloadData(completion: {

})

서브 클래스를 사용하고 있는지 확인하십시오 !!


0

이것은 나를 위해 일합니다.


- (void)viewDidLoad {
    [super viewDidLoad];

    int ScrollToIndex = 4;

    [self.UICollectionView performBatchUpdates:^{}
                                    completion:^(BOOL finished) {
                                             NSIndexPath *indexPath = [NSIndexPath indexPathForItem:ScrollToIndex inSection:0];
                                             [self.UICollectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
                                  }];

}


0

일괄 업데이트 내에서 collectionView를 다시로드 한 다음 완료 블록에서 부울 "finish"를 사용하여 완료되었는지 여부를 확인하기 만하면됩니다.

self.collectionView.performBatchUpdates({
        self.collectionView.reloadData()
    }) { (finish) in
        if finish{
            // Do your stuff here!
        }
    }

0

아래는 나를 위해 일한 유일한 접근 방식입니다.

extension UICollectionView {
    func reloadData(_ completion: (() -> Void)? = nil) {
        reloadData()
        guard let completion = completion else { return }
        layoutIfNeeded()
        completion()
    }
}

-1

이것이 Swift 3.0으로 문제를 해결 한 방법입니다.

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if !self.collectionView.visibleCells.isEmpty {
        // stuff
    }
}

작동하지 않습니다. VisibleCells는 첫 번째 셀이로드 되 자마자 내용을 갖게됩니다. 문제는 마지막 셀이로드 된시기를 알고 싶다는 것입니다.
Travis M.

-9

이 시도:

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return _Items.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell;
    //Some cell stuff here...

    if(indexPath.row == _Items.count-1){
       //THIS IS THE LAST CELL, SO TABLE IS LOADED! DO STUFF!
    }

    return cell;
}

2
그것은 cellFor가 해당 셀이 표시 될 것입니다 경우에만 전화를받을 것이다, 정확하지, 그 경우에 마지막으로 볼 수있는 항목은 ... 항목의 총 수와 동일로하지 않습니다
Jirune

-10

이렇게 할 수 있습니다 ...

  - (void)reloadMyCollectionView{

       [myCollectionView reload];
       [self performSelector:@selector(myStuff) withObject:nil afterDelay:0.0];

   }

  - (void)myStuff{
     // Do your stuff here. This will method will get called once your collection view get loaded.

    }

이 코드가 질문에 답할 수 있지만 질문에 대한 이유 및 / 또는 답변 방법 에 대한 추가 컨텍스트를 제공 하면 장기적인 가치가 크게 향상됩니다. 제발 편집 약간의 설명을 추가 할 답변을.
Toby Speight
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.