Я должен выполнить некоторую операцию всякий раз, когда UICollectionView был загружен полностью, т.е. в это время должны быть вызваны все методы источника данных / макета UICollectionView. Откуда я это знаю ?? Есть ли какой-либо метод делегата, чтобы узнать загруженный статус UICollectionView?
Как узнать, что UICollectionView полностью загружен?
Ответы (21)
Это сработало для меня:
[self.collectionView reloadData];
[self.collectionView performBatchUpdates:^{}
completion:^(BOOL finished) {
/// collection-view finished reload
}];
Синтаксис Swift 4:
collectionView.reloadData()
collectionView.performBatchUpdates(nil, completion: {
(result) in
// ready
})
reloadData обновляет collectionView, поэтому он снова использует методы источника данных ... performBatchUpdates имеет прикрепленный блок завершения, поэтому, если оба выполняются в основном потоке, вы знаете, что любой код, который вставлен вместо /// collection-view finished reload, будет выполняться с новым и выложил collectionView
- person Magoo; 12.05.2016
collectionView.collectionViewLayout.collectionViewContentSize может ошибаться на этом этапе. Надежное решение: stackoverflow.com/a/39648633/479851
- person Sébastien; 09.06.2021
На самом деле это довольно просто.
Когда вы, например, вызываете метод 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 требуется 3 секунды для выполнения, обработка второго блока будет отложена на эти 3 секунды.
reloadData. В некоторых случаях второй асинхронный блок может быть добавлен в основную очередь до того, как код в reloadData решит поместить что-то в основной поток.
- person larromba; 11.08.2015
reloadData, синхронны. Следовательно, любой код, который вы ставите в очередь в основном потоке после вызова reloadData, запускается после его завершения.
- person dezinezync; 12.08.2015
reloadData теперь ставит в очередь загрузку ячеек в основном потоке (даже если вызывается из основного потока). Таким образом, ячейки фактически не загружаются (cellForRowAtIndexPath: не вызывается) после возврата reloadData. Код-оболочка, который вы хотите выполнить после загрузки ячеек в dispatch_async(dispatch_get_main..., и его вызов после вызова reloadData приведет к желаемому результату.
- person Tylerc230; 03.06.2016
Просто чтобы добавить к отличному ответу @dezinezync:
Swift 3+
collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
// your stuff here executing after collectionView has been layouted
}
Другой подход с использованием RxSwift / RxCocoa:
collectionView.rx.observe(CGSize.self, "contentSize")
.subscribe(onNext: { size in
print(size as Any)
})
.disposed(by: disposeBag)
performBatchUpdates не работал в моем случае, это работает. Ваше здоровье!
- person Zoltán; 08.02.2019
Делай это так:
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)
})
Как ответил dezinezync, вам нужно отправить в основную очередь блок кода после reloadData из UITableView или UICollectionView, и тогда этот блок будет выполняться после удаления ячеек из очереди
Чтобы сделать это более прямым при использовании, я бы использовал такое расширение:
extension UICollectionView {
func reloadData(_ completion: @escaping () -> Void) {
reloadData()
DispatchQueue.main.async { completion() }
}
}
Это также может быть реализовано в UITableView.
Попробуйте принудительно выполнить синхронный проход макета через layoutIfNeeded () сразу после вызова reloadData (). Кажется, работает как для UICollectionView, так и для UITableView на iOS 12.
collectionView.reloadData()
collectionView.layoutIfNeeded()
// cellForItem/sizeForItem calls should be complete
completion?()
Я просто сделал следующее, чтобы выполнить что-либо после перезагрузки представления коллекции. Вы можете использовать этот код даже в ответе API.
self.collectionView.reloadData()
DispatchQueue.main.async {
// Do Task after collection view is reloaded
}
Лучшее решение, которое я нашел до сих пор, - использовать CATransaction для обработки завершения.
Swift 5:
CATransaction.begin()
CATransaction.setCompletionBlock {
// UICollectionView is ready
}
collectionView.reloadData()
CATransaction.commit()
Обновлено. Приведенное выше решение в некоторых случаях работает, а в некоторых - нет. В итоге я использовал принятый ответ, и это определенно самый стабильный и проверенный способ. Вот версия Swift 5:
private var contentSizeObservation: NSKeyValueObservation?
contentSizeObservation = collectionView.observe(\.contentSize) { [weak self] _, _ in
self?.contentSizeObservation = nil
completion()
}
collectionView.reloadData()
SWIFT 5
override func viewDidLoad() {
super.viewDidLoad()
// "collectionViewDidLoad" for transitioning from product's cartView to it's cell in that view
self.collectionView?.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let observedObject = object as? UICollectionView, observedObject == self.collectionView {
print("collectionViewDidLoad")
self.collectionView?.removeObserver(self, forKeyPath: "contentSize")
}
}
Это работает для меня:
__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
}
Действие по-прежнему будет выполняться несколько раз, как количество видимых ячеек в исходном состоянии. но во всех этих вызовах у вас будет одинаковое количество видимых ячеек (все они). И логический флаг предотвратит его повторный запуск после того, как пользователь начал взаимодействовать с представлением коллекции.
Просто перезагрузите collectionView внутри пакетных обновлений, а затем проверьте в блоке завершения, завершен он или нет, с помощью логического «finish».
self.collectionView.performBatchUpdates({
self.collectionView.reloadData()
}) { (finish) in
if finish{
// Do your stuff here!
}
}
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
}
}
Затем звоните так внутри своего ВК
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];
}];
}
Ниже приведен единственный подход, который у меня сработал.
extension UICollectionView {
func reloadData(_ completion: (() -> Void)? = nil) {
reloadData()
guard let completion = completion else { return }
layoutIfNeeded()
completion()
}
}
Большинство решений здесь ненадежны или имеют недетерминированное поведение (что может вызывать случайные ошибки) из-за сбивающего с толку асинхронного характера UICollectionView.
Надежным решением является создание подкласса UICollectionView для запуска блока завершения в конце layoutSubviews().
Код в Objectice-C: https://stackoverflow.com/a/39648633
Код в Swift: https://stackoverflow.com/a/39798079
Вот как я решил проблему со 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.
}
[self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL]; в -viewWillDisappear:animated:.
- person pxpgraphics; 18.06.2015