ฉันจะรู้ได้อย่างไรว่า 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

4
@ericosg เพิ่ม[self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];ไป-viewWillDisappear:animated:เช่นกัน
pxpgraphics

ยอดเยี่ยม! ขอบคุณมาก!
Abdo

41
ขนาดเนื้อหาจะเปลี่ยนไปเมื่อเลื่อนด้วยดังนั้นจึงไม่น่าเชื่อถือ
Ryan Heitner

ฉันได้ลองดูคอลเลกชันซึ่งรับข้อมูลโดยใช้วิธี async rest ซึ่งหมายความว่า Visible Cells จะว่างเปล่าเสมอเมื่อวิธีนี้เริ่มทำงานหมายความว่าฉันเลื่อนไปที่รายการในคอลเลกชันเมื่อเริ่มต้น
Chucky

156

สิ่งนี้ใช้ได้ผลสำหรับฉัน:

[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
ทำงานให้ฉันบน iOS 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

จริงๆแล้วมันค่อนข้างง่ายมาก

เมื่อคุณเรียกใช้เมธอด reloadData ของ UICollectionView หรือเมธอด 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 วินาทีในการดำเนินการการประมวลผลของบล็อกที่สองจะถูกเลื่อนออกไปโดย 3s เหล่านั้น


2
ฉันเพิ่งทดสอบสิ่งนี้จริงๆแล้วบล็อกกำลังถูกเรียกก่อนวิธีการมอบหมายอื่น ๆ ทั้งหมดของฉัน จึงไม่ได้ผล?
taylorcressy

@taylorcressy เดี๋ยวก่อนฉันได้อัปเดตโค้ดบางอย่างที่ฉันใช้ตอนนี้ใน 2 แอปที่ใช้งานจริง รวมคำอธิบายไว้ด้วย
dezinezync

ไม่ได้ผลสำหรับฉัน เมื่อฉันเรียกใช้ reloadData collectionView จะร้องขอเซลล์หลังจากการดำเนินการของบล็อกที่สอง ฉันมีปัญหาเหมือนกันกับ @taylorcressy ดูเหมือนจะมี
Raphael

1
คุณลองวางสายบล็อกที่สองภายในครั้งแรกได้ไหม ไม่ได้รับการยืนยัน แต่อาจใช้งานได้
dezinezync

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

ฉันได้รับการเลื่อนแบบกะพริบพยายามทำให้คอลเลกชันดูเลื่อนในตอนท้ายหลังจากแทรกรายการสิ่งนี้ได้รับการแก้ไขแล้วขอบคุณ
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

ลองบังคับใช้เลย์เอาต์แบบซิงโครนัสผ่าน layoutIfNeeded () ทันทีหลังจากเรียก reloadData () ดูเหมือนว่าจะใช้ได้กับทั้ง UICollectionView และ UITableView บน iOS 12

collectionView.reloadData()
collectionView.layoutIfNeeded() 

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

ขอบคุณ! ฉันมองหาทางออกของปัญหานั้นมานานแล้ว
Trzy Gracje

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 ใหม่ภายในการอัปเดตแบตช์จากนั้นตรวจสอบในบล็อกการเสร็จสิ้นว่าเสร็จสิ้นหรือไม่ด้วยความช่วยเหลือของบูลีน "เสร็จสิ้น"

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.