Как узнать, что UICollectionView полностью загружен?

Я должен выполнить некоторую операцию всякий раз, когда UICollectionView был загружен полностью, т.е. в это время должны быть вызваны все методы источника данных / макета UICollectionView. Откуда я это знаю ?? Есть ли какой-либо метод делегата, чтобы узнать загруженный статус UICollectionView?


person Jirune    schedule 24.12.2012    source источник


Ответы (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
})
person myeyesareblind    schedule 05.06.2015
comment
Работал у меня на iOS 9 - person Magoo; 26.04.2016
comment
@ G.Abhisek Errrr, конечно .... что тебе нужно объяснить? Первая строка reloadData обновляет collectionView, поэтому он снова использует методы источника данных ... performBatchUpdates имеет прикрепленный блок завершения, поэтому, если оба выполняются в основном потоке, вы знаете, что любой код, который вставлен вместо /// collection-view finished reload, будет выполняться с новым и выложил collectionView - person Magoo; 12.05.2016
comment
Не работает для меня, когда у collectionView были ячейки с динамическим размером - person ingaham; 08.08.2016
comment
Этот метод - прекрасное решение без головной боли. - person arjavlad; 07.03.2017
comment
@ingaham Это отлично работает для меня с ячейками динамического размера, Swift 4.2 IOS 12 - person Romulo BM; 04.01.2019
comment
@ingaham [self.collectionView insertItemsAtIndexPaths: indexSet]; dispatch_async (dispatch_get_main_queue (), ^ {[self.collectionView scrollToItemsAtIndexPaths: indexSet scrollPosition: NSCollectionViewScrollPositionBottom];}); - person prabhu; 14.02.2019
comment
У меня работает в iOS 11. Спасибо. - person userx; 13.03.2019
comment
У меня работает с ячейками динамического размера (AutoLayout) на iOS 13.4 - person Skoua; 13.04.2020
comment
ОСТЕРЕГАТЬСЯ. Это решение имеет случайное поведение. Иногда макет не завершается, когда выполняется блок завершения, поэтому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 секунды.

person dezinezync    schedule 19.12.2013
comment
На самом деле я только что это проверил. Блок вызывается раньше всех моих других методов делегата. Значит, не работает? - person taylorcressy; 06.08.2014
comment
@taylorcressy, привет, я обновил код, который сейчас использую в двух производственных приложениях. Также включены объяснения. - person dezinezync; 06.08.2014
comment
У меня не работает. Когда я вызываю reloadData, collectionView запрашивает ячейки после выполнения второго блока. Так что у меня та же проблема, что и у @taylorcressy. - person Raphael; 17.09.2014
comment
Можете ли вы попробовать поместить второй вызов блока в первый? Не проверено, но может работать. - person dezinezync; 18.09.2014
comment
Не думаю, что это правильное решение. Я думаю, что это может работать только иногда, потому что вы не знаете, что происходит под капотом reloadData. В некоторых случаях второй асинхронный блок может быть добавлен в основную очередь до того, как код в reloadData решит поместить что-то в основной поток. - person larromba; 11.08.2015
comment
@larromba AFAIK, все операции, которые происходят внутри reloadData, синхронны. Следовательно, любой код, который вы ставите в очередь в основном потоке после вызова reloadData, запускается после его завершения. - person dezinezync; 12.08.2015
comment
@dezinezync хорошо, круто. Было бы интересно узнать, почему некоторые люди обнаружили, что тогда это не работает. - person larromba; 12.08.2015
comment
@larromba да, конечно. В таких ситуациях помогут только примеры кода. - person dezinezync; 13.08.2015
comment
У меня это не работает. У меня есть данные, поступающие в представление коллекции, и в первом разделе я выполняю перезагрузку данных, а последующие данные - в разделе вставки. Проблема возникает, когда два вызова находятся слишком близко друг к другу. Возможно, данные перезагрузки не синхронны - person Ryan Heitner; 17.08.2015
comment
@ryan: вы пробовали обернуть оба внутри блока? - person dezinezync; 18.08.2015
comment
Что я сделал, так это поместил @synchronized в вызывающий метод. Я также поставил флаг в блоках завершения collectionView performBatchUpdates: и проигнорировал мою вставку и использовал перезагрузку, если флаг завершения не был установлен. Кажется, что это работает в 99% случаев. - person Ryan Heitner; 18.08.2015
comment
В документации сказано, что для performBatchUpdates: блок обработчика завершения, выполняемый после завершения всех операций. Этот блок принимает один логический параметр, который содержит значение YES, если все связанные анимации завершились успешно, или NO, если они были прерваны. Этот параметр может быть нулевым. Так что по идее флаг должен работать - person Ryan Heitner; 18.08.2015
comment
@ryan: не могли бы вы выложить где-нибудь этот образец, чтобы я тоже мог в нем покопаться? Я заинтригован этим. - person dezinezync; 18.08.2015
comment
Зависит от того, что вы хотите знать ... ContentSize в настоящее время может быть неточным - person Magoo; 26.04.2016
comment
reloadData теперь ставит в очередь загрузку ячеек в основном потоке (даже если вызывается из основного потока). Таким образом, ячейки фактически не загружаются (cellForRowAtIndexPath: не вызывается) после возврата reloadData. Код-оболочка, который вы хотите выполнить после загрузки ячеек в dispatch_async(dispatch_get_main..., и его вызов после вызова reloadData приведет к желаемому результату. - person Tylerc230; 03.06.2016
comment
@taylorcressy Что касается того, почему это не работает. Следите за комментариями, начиная с здесь. Суть его такова: Основной поток запускает NSRunLoop. Цикл выполнения имеет разные фазы, и вы можете запланировать обратный вызов для определенной фазы (используя CFRunLoopObserver). UIKit планирует размещение макета на более позднем этапе, после того, как ваш обработчик событий вернется. Но также не забудьте прочитать ответ. - person Honey; 31.12.2017

Просто чтобы добавить к отличному ответу @dezinezync:

Swift 3+

collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
    // your stuff here executing after collectionView has been layouted
}
person nikans    schedule 22.09.2017
comment
@nikas это мы должны добавить ввиду появилось !! - person SARATH SASI; 12.10.2017
comment
Не обязательно, вы можете вызывать его из любого места, где хотите что-то сделать, когда макет наконец загружен. - person nikans; 07.12.2017
comment
Я получал мерцание прокрутки, пытаясь заставить мою collectionView прокручивать в конце после вставки элементов, это исправило это, спасибо. - person Skoua; 27.07.2019

Другой подход с использованием RxSwift / RxCocoa:

        collectionView.rx.observe(CGSize.self, "contentSize")
            .subscribe(onNext: { size in
                print(size as Any)
            })
            .disposed(by: disposeBag)
person Chinh Nguyen    schedule 02.01.2019
comment
Мне это нравится. performBatchUpdates не работал в моем случае, это работает. Ваше здоровье! - person Zoltán; 08.02.2019
comment
@ MichałZiobro, А можно где нибудь обновить код? Метод должен вызываться только при изменении contentSize? Так что, если вы не меняете его на каждом свитке, это должно сработать! - person Chinh Nguyen; 29.05.2019
comment
это работает так здорово для меня, что он не только прокручивает нужную ячейку, но и продолжает прокручивать назад (потому что он постоянно работает с изменениями содержимого), я думаю, это не ошибка, а особенность xD. Однако это не совсем понятное решение - person Zaporozhchenko Oleksandr; 27.05.2021

Делай это так:

       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)
        })
person Darko    schedule 10.09.2016

Как ответил dezinezync, вам нужно отправить в основную очередь блок кода после reloadData из UITableView или UICollectionView, и тогда этот блок будет выполняться после удаления ячеек из очереди

Чтобы сделать это более прямым при использовании, я бы использовал такое расширение:

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

Это также может быть реализовано в UITableView.

person Matheus Rocco    schedule 02.11.2018

Попробуйте принудительно выполнить синхронный проход макета через layoutIfNeeded () сразу после вызова reloadData (). Кажется, работает как для UICollectionView, так и для UITableView на iOS 12.

collectionView.reloadData()
collectionView.layoutIfNeeded() 

// cellForItem/sizeForItem calls should be complete
completion?()
person tyler    schedule 01.03.2019
comment
Спасибо чувак! Я довольно долго искал решение этой проблемы. - person Trzy Gracje; 28.06.2019

Я просто сделал следующее, чтобы выполнить что-либо после перезагрузки представления коллекции. Вы можете использовать этот код даже в ответе API.

self.collectionView.reloadData()

DispatchQueue.main.async {
   // Do Task after collection view is reloaded                
}
person Asad Jamil    schedule 04.02.2020

Лучшее решение, которое я нашел до сих пор, - использовать 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()
person MaxMedvedev    schedule 16.07.2020

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")
    }
}
person AndrewK    schedule 04.12.2020

Это работает для меня:

__weak typeof(self) wself= self;
[self.contentCollectionView performBatchUpdates:^{
    [wself.contentCollectionView reloadData];
} completion:^(BOOL finished) {
    [wself pageViewCurrentIndexDidChanged:self.contentCollectionView];
}];
person jAckOdE    schedule 12.01.2016
comment
ерунда ... это никак не закончил выкладку. - person Magoo; 26.04.2016

Мне нужно было выполнить какое-то действие со всеми видимыми ячейками, когда представление коллекции загружается до того, как оно станет видимым для пользователя, я использовал:

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
}

Действие по-прежнему будет выполняться несколько раз, как количество видимых ячеек в исходном состоянии. но во всех этих вызовах у вас будет одинаковое количество видимых ячеек (все они). И логический флаг предотвратит его повторный запуск после того, как пользователь начал взаимодействовать с представлением коллекции.

person gutte    schedule 06.05.2018

Просто перезагрузите collectionView внутри пакетных обновлений, а затем проверьте в блоке завершения, завершен он или нет, с помощью логического «finish».

self.collectionView.performBatchUpdates({
        self.collectionView.reloadData()
    }) { (finish) in
        if finish{
            // Do your stuff here!
        }
    }
person niku    schedule 06.12.2019
comment
У меня не работает, по крайней мере, в симуляторе iOS 14.1. Завершение никогда не называется. См. Этот радар: openradar.me/48941363, который, кажется, имеет отношение. Также см. Этот SO, в котором говорится, что вызывать reloadData () из performBatchUpdates - не лучшая идея. Я возлагал такие большие надежды на этот элегантный подход ... - person Smartcat; 05.01.2021

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: {

})

Убедитесь, что вы используете подкласс !!

person Jon Vogel    schedule 17.01.2018

Эта работа для меня:


- (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];
                                  }];

}

person chrisz    schedule 23.01.2019

Ниже приведен единственный подход, который у меня сработал.

extension UICollectionView {
    func reloadData(_ completion: (() -> Void)? = nil) {
        reloadData()
        guard let completion = completion else { return }
        layoutIfNeeded()
        completion()
    }
}
person Ashok    schedule 16.08.2020
comment
Думаю не работает, потому что reloaData сразу отдаст управление - person Zaporozhchenko Oleksandr; 27.05.2021

Большинство решений здесь ненадежны или имеют недетерминированное поведение (что может вызывать случайные ошибки) из-за сбивающего с толку асинхронного характера UICollectionView.

Надежным решением является создание подкласса UICollectionView для запуска блока завершения в конце layoutSubviews().

Код в Objectice-C: https://stackoverflow.com/a/39648633

Код в Swift: https://stackoverflow.com/a/39798079

person Sébastien    schedule 09.06.2021

Вот как я решил проблему со Swift 3.0:

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

    if !self.collectionView.visibleCells.isEmpty {
        // stuff
    }
}
person landonandrey    schedule 16.02.2017
comment
Это не сработает. VisibleCells будет иметь контент, как только будет загружена первая ячейка ... проблема в том, что мы хотим знать, когда была загружена последняя ячейка. - person Travis M.; 07.04.2017

Попробуй это:

- (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;
}
person Rocker    schedule 07.11.2013
comment
Это неверно, cellFor будет вызываться только в том случае, если эта ячейка будет видна, в этом случае последний видимый элемент не будет равен общему количеству элементов ... - person Jirune; 08.11.2013

Вы можете сделать вот так ...

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

    }
person Swapnil    schedule 09.04.2013
comment
Хотя этот код может ответить на вопрос, предоставление дополнительного контекста относительно почему и / или как он отвечает на вопрос, значительно улучшит его долгосрочную ценность. Пожалуйста, отредактируйте свой ответ, чтобы добавить пояснения. - person Toby Speight; 14.06.2016

person    schedule
comment
это вылетает у меня, когда я возвращаюсь назад, в остальном работает хорошо. - person ericosg; 28.04.2015
comment
@ericosg также добавляет [self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL]; в -viewWillDisappear:animated:. - person pxpgraphics; 18.06.2015
comment
Блестяще! Большое тебе спасибо! - person Abdo; 19.06.2015
comment
Размер содержимого также изменяется при прокрутке, поэтому он ненадежен. - person Ryan Heitner; 17.08.2015
comment
Я пробовал использовать представление коллекции, которое получает данные с помощью метода async rest. Это означает, что при срабатывании этого метода Visible Cells всегда пуста, то есть при запуске я прокручиваю до элемента в коллекции. - person Chucky; 06.09.2016
comment
Используйте этот метод с осторожностью, он ненадежен. UIKit в основном НЕ совместим с KVO. - person Vincent Sit; 30.09.2018
comment
Бесполезно, если вы прокручиваете collectionView - person TomSawyer; 08.06.2019