web-dev-qa-db-fra.com

Comment savoir si UICollectionView a été chargé complètement?

Je dois effectuer certaines opérations chaque fois que UICollectionView a été chargé complètement, c'est-à-dire qu'à ce moment-là, toutes les méthodes de source de données/de mise en forme de UICollectionView doivent être appelées. Comment puis-je savoir ça ?? Existe-t-il une méthode déléguée pour connaître le statut chargé UICollectionView?

72
Jirune
// 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];
}
53
Cullen SUN

Cela a fonctionné pour moi:

[self.collectionView reloadData];
[self.collectionView performBatchUpdates:^{}
                              completion:^(BOOL finished) {
                                  /// collection-view finished reload
                              }];

Syntaxe de Swift 4:

self.collectionView.reloadData()
self.collectionView.performBatchUpdates(nil, completion: {
    (result) in
    // ready
})
110
myeyesareblind

C'est en fait plutôt très simple. 

Lorsque vous appelez par exemple la méthode reloadData de UICollectionView ou la méthode invalidateLayout de sa présentation, vous procédez comme suit:

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

Pourquoi ça marche:

Le fil principal (où nous devons effectuer toutes les mises à jour de l'interface utilisateur) contient la file d'attente principale, qui est de nature série, c'est-à-dire qu'elle fonctionne à la manière FIFO. Ainsi, dans l'exemple ci-dessus, le premier bloc est appelé. La méthode reloadData est invoquée, suivie de tout ce qui se trouve dans le deuxième bloc. 

Maintenant, le fil principal bloque également. Donc, si vous êtes reloadData prend 3s à exécuter, le traitement du deuxième bloc sera différé par ces 3s. 

25
dezinezync

Juste pour ajouter à une excellente réponse @dezinezync:

Swift 3+

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

Fais-le comme ça:

       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
Darko

Une approche différente avec RxSwift/RxCocoa: 

        collectionView.rx.observe(CGSize.self, "contentSize")
            .subscribe(onNext: { size in
                print(size as Any)
            })
            .disposed(by: disposeBag)
2
Chinh Nguyen

Cela fonctionne pour moi:

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

Lorsque vous répondez à dezinezync , vous devez envoyer à la file principale un bloc de code après reloadData à partir de UITableView ou UICollectionView, puis ce bloc sera exécuté après la mise en file d'attente des cellules

Afin de rendre cela plus simple lors de l'utilisation, j'utiliserais une extension comme celle-ci:

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

Il peut également être implémenté sur une UITableView

1
Matheus Rocco

Ce travail pour moi:


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

J'avais besoin d'une action sur toutes les cellules visibles lorsque la vue de collection était chargée avant qu'elle ne soit visible par l'utilisateur. J'ai utilisé:

public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    if shouldPerformBatch {
        self.collectionView.performBatchUpdates(nil) { completed in
            self.modifyVisibleCells()
        }
    }
}

Faites attention que cela sera appelé lors du défilement de la vue de la collection, afin d'éviter cette surcharge, j'ai ajouté:

private var souldPerformAction: Bool = true

et dans l'action elle-même:

private func modifyVisibleCells() {
    if self.shouldPerformAction {
        // perform action
        ...
        ...
    }
    self.shouldPerformAction = false
}

L'action sera toujours effectuée plusieurs fois, sous la forme du nombre de cellules visibles à l'état initial. mais sur tous ces appels, vous aurez le même nombre de cellules visibles (toutes). Et le drapeau booléen l'empêchera de s'exécuter à nouveau après que l'utilisateur ait commencé à interagir avec la vue de collection.

0
gutte

Def faire ceci:

//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
    }

}

Puis appelez comme ceci dans votre VC

let collection = MyCollectionView()

self.collection.reloadData(completion: {

})

Assurez-vous que vous utilisez la sous-classe !!

0
Jon Vogel

Essayez de forcer une présentation synchrone via layoutIfNeeded () juste après l'appel reloadData () Semble fonctionner à la fois pour UICollectionView et UITableView sur iOS 12. 

collectionView.reloadData()
collectionView.layoutIfNeeded() 

// cellForItem/sizeForItem calls should be complete
completion?()
0
tyler