ฉันต้องดำเนินการบางอย่างเมื่อใดก็ตามที่โหลด UICollectionView อย่างสมบูรณ์นั่นคือในเวลานั้นควรเรียกวิธีการแหล่งข้อมูล / เค้าโครงของ UICollectionView ทั้งหมด จะรู้ได้อย่างไร ?? มีวิธีการมอบหมายเพื่อทราบสถานะการโหลด UICollectionView หรือไม่?
ฉันต้องดำเนินการบางอย่างเมื่อใดก็ตามที่โหลด UICollectionView อย่างสมบูรณ์นั่นคือในเวลานั้นควรเรียกวิธีการแหล่งข้อมูล / เค้าโครงของ UICollectionView ทั้งหมด จะรู้ได้อย่างไร ?? มีวิธีการมอบหมายเพื่อทราบสถานะการโหลด UICollectionView หรือไม่?
คำตอบ:
// 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];
}
[self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];
ไป-viewWillDisappear:animated:
เช่นกัน
สิ่งนี้ใช้ได้ผลสำหรับฉัน:
[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
})
reloadData
รีเฟรชcollectionView
ดังนั้นจึงใช้วิธีการแหล่งข้อมูลอีกครั้ง ... performBatchUpdates
มีการแนบบล็อกการเสร็จสิ้นดังนั้นหากทั้งสองถูกดำเนินการในเธรดหลักคุณรู้ว่ารหัสใด ๆ ที่วางไว้/// collection-view finished reload
จะดำเนินการด้วยการจัดวางใหม่และจัดวางcollectionView
จริงๆแล้วมันค่อนข้างง่ายมาก
เมื่อคุณเรียกใช้เมธอด 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 เหล่านั้น
reloadData
ตอนนี้จัดเตรียมการโหลดเซลล์บนเธรดหลัก (แม้ว่าจะเรียกจากเธรดหลัก) ดังนั้นเซลล์จึงไม่ถูกโหลดจริง ๆ ( cellForRowAtIndexPath:
ไม่ได้ถูกเรียก) หลังจากreloadData
ส่งคืน การตัดรหัสที่คุณต้องการให้ดำเนินการหลังจากโหลดเซลล์ของคุณdispatch_async(dispatch_get_main...
แล้วและการเรียกใช้หลังจากที่คุณโทรไปreloadData
จะได้ผลลัพธ์ที่ต้องการ
เพียงเพื่อเพิ่มคำตอบที่ยอดเยี่ยมของ @dezinezync:
Swift 3+
collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
// your stuff here executing after collectionView has been layouted
}
ทำเช่นนี้:
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)
})
ลองบังคับใช้เลย์เอาต์แบบซิงโครนัสผ่าน layoutIfNeeded () ทันทีหลังจากเรียก reloadData () ดูเหมือนว่าจะใช้ได้กับทั้ง UICollectionView และ UITableView บน iOS 12
collectionView.reloadData()
collectionView.layoutIfNeeded()
// cellForItem/sizeForItem calls should be complete
completion?()
ตามที่dezinezyncตอบสิ่งที่คุณต้องการคือส่งไปยังคิวหลักบล็อกของโค้ดหลังจากreloadData
a UITableView
หรือUICollectionView
จากนั้นบล็อกนี้จะถูกดำเนินการหลังจากที่เซลล์เลิกจัดคิว
เพื่อให้ตรงมากขึ้นเมื่อใช้ฉันจะใช้ส่วนขยายดังนี้:
extension UICollectionView {
func reloadData(_ completion: @escaping () -> Void) {
reloadData()
DispatchQueue.main.async { completion() }
}
}
และยังสามารถนำไปใช้งานได้อีกUITableView
ด้วย
การเข้าใกล้ที่แตกต่างกันโดยใช้ RxSwift / RxCocoa:
collectionView.rx.observe(CGSize.self, "contentSize")
.subscribe(onNext: { size in
print(size as Any)
})
.disposed(by: disposeBag)
performBatchUpdates
ไม่ได้ผลในกรณีของฉันอันนี้ทำ ไชโย!
สิ่งนี้ใช้ได้กับฉัน:
__weak typeof(self) wself= self;
[self.contentCollectionView performBatchUpdates:^{
[wself.contentCollectionView reloadData];
} completion:^(BOOL finished) {
[wself pageViewCurrentIndexDidChanged:self.contentCollectionView];
}];
ฉันต้องการดำเนินการบางอย่างกับเซลล์ที่มองเห็นทั้งหมดเมื่อโหลดมุมมองคอลเลกชันก่อนที่ผู้ใช้จะมองเห็นฉันใช้:
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
}
การดำเนินการจะยังคงดำเนินการหลายครั้งเนื่องจากจำนวนเซลล์ที่มองเห็นได้ในสถานะเริ่มต้น แต่ในการโทรทั้งหมดนั้นคุณจะมีจำนวนเซลล์ที่มองเห็นได้เท่ากัน (ทั้งหมด) และแฟล็กบูลีนจะป้องกันไม่ให้รันอีกครั้งหลังจากที่ผู้ใช้เริ่มโต้ตอบกับมุมมองคอลเลกชัน
ฉันเพิ่งทำสิ่งต่อไปนี้เพื่อดำเนินการทุกอย่างหลังจากโหลดมุมมองคอลเลกชันใหม่ คุณสามารถใช้รหัสนี้ได้แม้ในการตอบสนองของ API
self.collectionView.reloadData()
DispatchQueue.main.async {
// Do Task after collection view is reloaded
}
ทางออกที่ดีที่สุดที่ฉันพบคือการใช้CATransaction
เพื่อจัดการกับความสำเร็จ
สวิฟต์ 5 :
CATransaction.begin()
CATransaction.setCompletionBlock {
// UICollectionView is ready
}
collectionView.reloadData()
CATransaction.commit()
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: {
})
ตรวจสอบให้แน่ใจว่าคุณใช้คลาสย่อย !!
งานนี้สำหรับฉัน:
- (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];
}];
}
เพียงโหลด collectionView ใหม่ภายในการอัปเดตแบตช์จากนั้นตรวจสอบในบล็อกการเสร็จสิ้นว่าเสร็จสิ้นหรือไม่ด้วยความช่วยเหลือของบูลีน "เสร็จสิ้น"
self.collectionView.performBatchUpdates({
self.collectionView.reloadData()
}) { (finish) in
if finish{
// Do your stuff here!
}
}
ด้านล่างนี้เป็นแนวทางเดียวที่ใช้ได้ผลสำหรับฉัน
extension UICollectionView {
func reloadData(_ completion: (() -> Void)? = nil) {
reloadData()
guard let completion = completion else { return }
layoutIfNeeded()
completion()
}
}
นี่คือวิธีที่ฉันแก้ไขปัญหาด้วย Swift 3.0:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.collectionView.visibleCells.isEmpty {
// stuff
}
}
ลองสิ่งนี้:
- (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;
}
ทำได้แบบนี้ ...
- (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.
}