Làm cách nào để biết rằng UICollectionView đã được tải hoàn chỉnh?


98

Tôi phải thực hiện một số thao tác bất cứ khi nào UICollectionView đã được tải hoàn chỉnh, tức là tại thời điểm đó tất cả các phương thức bố cục / nguồn dữ liệu của UICollectionView sẽ được gọi. Làm sao tôi biết được điều đó ?? Có phương pháp ủy quyền nào để biết trạng thái tải của UICollectionView không?

Câu trả lời:


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
điều này gặp sự cố đối với tôi khi tôi điều hướng trở lại, nếu không thì hoạt động tốt.
ericosg

4
@ericosg cũng thêm [self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];vào -viewWillDisappear:animated:.
pxpgraphics

Xuất sắc! Cảm ơn rât nhiều!
Abdo

41
Kích thước nội dung cũng thay đổi khi cuộn nên không đáng tin cậy.
Ryan Heitner

Tôi đã thử chế độ xem bộ sưu tập lấy dữ liệu của nó bằng cách sử dụng phương pháp nghỉ ngơi không đồng bộ. Điều này có nghĩa là Ô hiển thị luôn trống khi phương thức này kích hoạt, nghĩa là tôi cuộn đến một mục trong bộ sưu tập khi bắt đầu.
Chucky

155

Điều này đã làm việc cho tôi:

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

Cú pháp Swift 4:

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

1
Làm việc cho tôi trên iOS 9
Magoo

5
@ G.Abhisek Errrr, chắc chắn .... bạn cần giải thích điều gì? Dòng đầu tiên reloadDatalàm mới collectionViewđể nó dựa trên các phương thức nguồn dữ liệu một lần nữa ... dòng performBatchUpdatesnày có một khối hoàn thành được đính kèm, vì vậy nếu cả hai được thực hiện trên chuỗi chính, bạn biết bất kỳ mã nào được đặt vào /// collection-view finished reloadsẽ thực thi với một mã mới và bố collectionView
cục

8
Không làm việc cho tôi khi collectionView có tế bào có kích thước động
ingaham

2
Phương pháp này là giải pháp hoàn hảo mà không có bất kỳ đau đầu.
arjavlad

1
@ingaham này hoạt động hoàn hảo cho tôi với các tế bào có kích thước động, Swift 4.2 IOS 12
Romulo BM

27

Nó thực sự khá đơn giản.

Ví dụ: khi bạn gọi phương thức reloadData của UICollectionView hoặc phương thức invalidateLayout của bố cục, bạn thực hiện như sau:

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

Tại sao điều này hoạt động:

Chuỗi chính (là nơi chúng ta nên thực hiện tất cả các cập nhật giao diện người dùng) chứa hàng đợi chính, có tính chất nối tiếp, tức là nó hoạt động theo kiểu FIFO. Vì vậy, trong ví dụ trên, khối đầu tiên được gọi, trong đó reloadDataphương thức của chúng ta đang được gọi, theo sau là bất kỳ thứ gì khác trong khối thứ hai.

Bây giờ luồng chính cũng đang bị chặn. Vì vậy, nếu bạn reloadDatamất 3 giây để thực thi, quá trình xử lý khối thứ hai sẽ bị hoãn lại bởi 3 giây đó.


2
Tôi thực sự vừa thử nghiệm điều này, Khối đang được gọi trước tất cả các phương thức đại biểu khác của tôi. Vì vậy, nó không hoạt động?
taylorcressy

@taylorcressy này, tôi đã cập nhật mã, thứ mà tôi hiện đang sử dụng trong 2 ứng dụng sản xuất. Cũng bao gồm lời giải thích.
dezinezync

Không hiệu quả với tôi. Khi tôi gọi reloadData, collectionView sẽ yêu cầu các ô sau khi thực thi khối thứ hai. Vì vậy, tôi đang gặp vấn đề tương tự như @taylorcressy dường như có.
Raphael

1
Bạn có thể thử đặt lệnh gọi khối thứ hai trong cuộc gọi đầu tiên không? Chưa được xác minh, nhưng có thể hoạt động.
dezinezync

6
reloadDatabây giờ xếp hàng chờ tải các ô trên luồng chính (ngay cả khi được gọi từ luồng chính). Vì vậy, các ô không thực sự được tải ( cellForRowAtIndexPath:không được gọi) sau khi reloadDatatrả về. Mã gói bạn muốn được thực thi sau khi các ô của bạn được tải vào dispatch_async(dispatch_get_main...và gọi mã đó sau cuộc gọi của bạn reloadDatasẽ đạt được kết quả mong muốn.
Tylerc230

12

Chỉ để thêm vào một câu trả lời @dezinezync tuyệt vời:

Swift 3+

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

@nikas cái này chúng ta phải thêm vào đã xuất hiện !!
SARATH SASI

1
Không nhất thiết, bạn có thể gọi nó từ bất cứ đâu bạn muốn làm điều gì đó khi bố cục được tải cuối cùng.
nikans

Tôi đã nhận được nhấp nháy cuộn khi cố gắng làm cho bộ sưu tập của tôi cuộn ở cuối sau khi chèn các mục, điều này đã khắc phục sự cố, cảm ơn.
Skoua

6

Làm như thế này:

       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

Hãy thử buộc chuyển một bố cục đồng bộ qua layoutIfNeeded () ngay sau lệnh gọi reloadData (). Có vẻ như hoạt động cho cả UICollectionView và UITableView trên iOS 12.

collectionView.reloadData()
collectionView.layoutIfNeeded() 

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

Cảm ơn anh bạn! Tôi đã tìm kiếm giải pháp của vấn đề đó trong một thời gian khá dài.
Trzy Gracje

3

Như dezinezync đã trả lời, những gì bạn cần là gửi đến hàng đợi chính một khối mã sau reloadDatatừ UITableViewhoặc UICollectionViewvà sau đó khối này sẽ được thực thi sau khi các ô sắp xếp lại

Để làm cho điều này dễ dàng hơn khi sử dụng, tôi sẽ sử dụng một phần mở rộng như sau:

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

Nó cũng có thể được thực hiện với một UITableViewcũng


3

Một cách tiếp cận khác bằng cách sử dụng RxSwift / RxCocoa:

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

1
Tôi thích điều này. performBatchUpdateskhông hoạt động trong trường hợp của tôi, cái này làm được. Chúc mừng!
Zoltán

@ MichałZiobro, Bạn có thể cập nhật mã của mình ở đâu đó không? Phương thức chỉ nên được gọi khi contentSize đã thay đổi? Vì vậy, trừ khi bạn thay đổi nó trên mỗi cuộn, điều này sẽ hoạt động!
Chinh Nguyen

1

Điều này phù hợp với tôi:

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

2
vớ vẩn ... điều này vẫn chưa kết thúc theo bất kỳ cách nào.
Magoo

1

Tôi cần một số hành động được thực hiện trên tất cả các ô hiển thị khi chế độ xem bộ sưu tập được tải trước khi nó hiển thị cho người dùng, tôi đã sử dụng:

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

Hãy chú ý rằng điều này sẽ được gọi khi cuộn qua chế độ xem bộ sưu tập, vì vậy để ngăn chặn chi phí này, tôi đã thêm:

private var souldPerformAction: Bool = true

và trong chính hành động:

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

Hành động sẽ vẫn được thực hiện nhiều lần, vì số lượng ô hiển thị ở trạng thái ban đầu. nhưng trên tất cả các lệnh gọi đó, bạn sẽ có cùng một số ô hiển thị (tất cả chúng). Và cờ boolean sẽ ngăn nó chạy lại sau khi người dùng bắt đầu tương tác với chế độ xem bộ sưu tập.


1

Tôi chỉ làm như sau để thực hiện bất cứ điều gì sau khi chế độ xem bộ sưu tập được tải lại. Bạn có thể sử dụng mã này ngay cả trong phản hồi API.

self.collectionView.reloadData()

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

1

Giải pháp tốt nhất mà tôi đã tìm thấy cho đến nay là sử dụng CATransactionđể xử lý việc hoàn thành.

Swift 5 :

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

collectionView.reloadData()

CATransaction.commit()

0

Def làm điều này:

//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
    }

}

Sau đó, gọi như thế này bên trong VC của bạn

let collection = MyCollectionView()

self.collection.reloadData(completion: {

})

Hãy chắc chắn rằng bạn đang sử dụng lớp con !!


0

Công việc này đối với tôi:


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

Chỉ cần tải lại collectionView bên trong các bản cập nhật hàng loạt và sau đó kiểm tra khối hoàn thành xem nó đã hoàn thành hay chưa với sự trợ giúp của boolean "finish".

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

0

Dưới đây là cách tiếp cận duy nhất phù hợp với tôi.

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

-1

Đây là cách tôi giải quyết vấn đề với Swift 3.0:

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

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

Điều này sẽ không hoạt động. VisibleCells sẽ có nội dung ngay khi ô đầu tiên được tải ... vấn đề là chúng ta muốn biết ô cuối cùng đã được tải khi nào.
Travis M.

-9

Thử cái này:

- (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
Nó không phải là chính xác, cellFor sẽ được gọi chỉ khi tế bào có nghĩa là sẽ được hiển thị, trong trường hợp đó có thể nhìn thấy mục cuối cùng sẽ không được như tương đương với tổng số các mặt hàng ...
Jirune

-10

Bạn có thể làm như thế này ...

  - (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.

    }

Mặc dù mã này có thể trả lời câu hỏi, nhưng việc cung cấp thêm ngữ cảnh liên quan đến lý do và / hoặc cách nó trả lời câu hỏi sẽ cải thiện đáng kể giá trị lâu dài của nó. Vui lòng chỉnh sửa câu trả lời của bạn để thêm một số giải thích.
Toby Speight
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.