Як я можу знати, що 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

Блискуче! Велике спасибі!
Абдо

41
Розмір вмісту змінюється під час прокрутки, тому він не є надійним.
Райан Хайтнер

Я спробував переглянути колекцію, яка отримує дані, використовуючи метод асинхронного відпочинку. Це означає, що Visible Cells завжди порожні, коли цей метод спрацьовує, тобто я прокручую елемент у колекції на початку.
Чакі

155

Це працювало для мене:

[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 були динамічні розміри комірок
Інгахам

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
});

Чому це працює:

Основний потік (саме там ми повинні робити всі оновлення інтерфейсу користувача) містить основну чергу, що має серійний характер, тобто працює в режимі FIFO. Отже, у наведеному вище прикладі викликається перший блок, який викликає наш reloadDataметод, а потім щось інше у другому блоці.

Тепер основна нитка також блокує. Отже, якщо вам reloadDataпотрібно виконати 3s, обробка другого блоку буде відкладена цими 3s.


2
Я насправді щойно перевірив це, блок викликається перед усіма іншими моїми методами делегатів. То це не працює?
taylorcressy

@taylorcressy привіт, я оновив код, те, що я використовую зараз у 2 робочих програмах. Також включено пояснення.
dezinezync

Не працює для мене. Коли я викликаю reloadData, collectionView запитує комірки після виконання другого блоку. Отже, у мене така ж проблема, як у @taylorcressy.
Рафаель

1
Чи можете ви спробувати помістити другий блоковий виклик у перший? Не перевірено, але може спрацювати.
dezinezync

6
reloadDataТепер задається завантаження комірок на основну нитку (навіть якщо вона викликана з головної нитки). Отже, комірки фактично не завантажуються ( cellForRowAtIndexPath:не викликаються) після reloadDataповернення. Код обертання, який потрібно виконати після завантаження ваших комірок dispatch_async(dispatch_get_main...і виклику, який після вашого дзвінка reloadDataдосягне бажаного результату.
Tylerc230,

12

Просто щоб додати до чудової відповіді @dezinezync:

Свіфт 3+

collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
    // your stuff here executing after collectionView has been layouted
}

@nikas це ми мусимо додати, зважаючи на те !!
SARATH SASI

1
Необов’язково, ви можете зателефонувати йому звідки ви хочете щось зробити, коли макет остаточно завантажується.
nikans

Я отримував мерехтіння прокрутки, намагаючись зробити свою прокрутку collectionView наприкінці після вставлення елементів, це виправлено, дякую.
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 , вам потрібно відправити в основну чергу блок коду після reloadDataз 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не працював у моєму випадку, це робить це. Ура!
Золтан

@ MichałZiobro, Чи можете ви десь оновити код? Метод слід викликати лише тоді, коли змінено зміна розміру? Отже, якщо ви не зміните його на кожному прокручуванні, це має спрацювати!
Чін Нгуєн,

1

Це працює для мене:

__weak typeof(self) wself= self;
[self.contentCollectionView performBatchUpdates:^{
    [wself.contentCollectionView reloadData];
} completion:^(BOOL finished) {
    [wself pageViewCurrentIndexDidChanged:self.contentCollectionView];
}];

2
нісенітниця ... це жодним чином не закінчило викладати.
Магуо

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

Виконайте це:

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

})

Переконайтеся, що ви використовуєте підклас !!


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 матиме вміст, як тільки завантажується перша комірка ... проблема полягає в тому, що ми хочемо знати, коли завантажена остання комірка.
Тревіс М.

-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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.