Відповіді:
// 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
});
Чому це працює:
Основний потік (саме там ми повинні робити всі оновлення інтерфейсу користувача) містить основну чергу, що має серійний характер, тобто працює в режимі FIFO. Отже, у наведеному вище прикладі викликається перший блок, який викликає наш reloadData
метод, а потім щось інше у другому блоці.
Тепер основна нитка також блокує. Отже, якщо вам reloadData
потрібно виконати 3s, обробка другого блоку буде відкладена цими 3s.
reloadData
Тепер задається завантаження комірок на основну нитку (навіть якщо вона викликана з головної нитки). Отже, комірки фактично не завантажуються ( cellForRowAtIndexPath:
не викликаються) після reloadData
повернення. Код обертання, який потрібно виконати після завантаження ваших комірок dispatch_async(dispatch_get_main...
і виклику, який після вашого дзвінка reloadData
досягне бажаного результату.
Просто щоб додати до чудової відповіді @dezinezync:
Свіфт 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
з 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()
Виконайте це:
//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
}
}
Потім зателефонуйте так у вашому ПК
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];
}];
}
Ось як я вирішив проблему з 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.
}