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?
// 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];
}
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
})
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.
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
}
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)
})
Une approche différente avec RxSwift/RxCocoa:
collectionView.rx.observe(CGSize.self, "contentSize")
.subscribe(onNext: { size in
print(size as Any)
})
.disposed(by: disposeBag)
Cela fonctionne pour moi:
__weak typeof(self) wself= self;
[self.contentCollectionView performBatchUpdates:^{
[wself.contentCollectionView reloadData];
} completion:^(BOOL finished) {
[wself pageViewCurrentIndexDidChanged:self.contentCollectionView];
}];
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
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];
}];
}
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.
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 !!
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?()