L'animation de UICollectionView contentOffset n'affiche pas les cellules non visibles

Je travaille sur une fonctionnalité de type ticker et UICollectionView un UICollectionView . Il était à l'origine un scrollView, mais nous figurons une collectionViewView, il sera plus facile d'append / supprimer des cellules.

J'anime la collectionView avec les éléments suivants:

 - (void)beginAnimation { [UIView animateWithDuration:((self.collectionView.collectionViewLayout.collectionViewContentSize.width - self.collectionView.contentOffset.x) / 75) delay:0 options:(UIViewAnimationOptionCurveLinear | UIViewAnimationOptionRepeat | UIViewAnimationOptionBeginFromCurrentState) animations:^{ self.collectionView.contentOffset = CGPointMake(self.collectionView.collectionViewLayout.collectionViewContentSize.width, 0); } completion:nil]; } 

Cela fonctionne très bien pour la vue déroulante et l'animation est en cours avec la vue de collection. Cependant, seules les cellules visibles à la fin de l'animation sont réellement rendues. Ajuster le contentOffset ne provoque pas l' cellForItemAtIndexPath de cellForItemAtIndexPath . Comment puis-je get les cellules à rendre quand le contentOffset change?

EDIT: Pour un peu plus de reference (pas sûr si c'est beaucoup d'aide):

 - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { TickerElementCell *cell = (TickerElementCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"TickerElementCell" forIndexPath:indexPath]; cell.ticker = [self.fetchedResultsController objectAtIndexPath:indexPath]; return cell; } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { // ... [self loadTicker]; } - (void)loadTicker { // ... if (self.animating) { [self updateAnimation]; } else { [self beginAnimation]; } } - (void)beginAnimation { if (self.animating) { [self endAnimation]; } if ([self.tickerElements count] && !self.animating && !self.paused) { self.animating = YES; self.collectionView.contentOffset = CGPointMake(1, 0); [UIView animateWithDuration:((self.collectionView.collectionViewLayout.collectionViewContentSize.width - self.collectionView.contentOffset.x) / 75) delay:0 options:(UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionBeginFromCurrentState) animations:^{ self.collectionView.contentOffset = CGPointMake(self.collectionView.collectionViewLayout.collectionViewContentSize.width, 0); } completion:nil]; } } 

Vous devriez simplement append [self.view layoutIfNeeded]; à l'intérieur du bloc d'animation, comme ceci:

 [UIView animateWithDuration:((self.collectionView.collectionViewLayout.collectionViewContentSize.width - self.collectionView.contentOffset.x) / 75) delay:0 options:(UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionBeginFromCurrentState) animations:^{ self.collectionView.contentOffset = CGPointMake(self.collectionView.collectionViewLayout.collectionViewContentSize.width, 0); [self.view layoutIfNeeded]; } completion:nil]; 

Vous pouvez essayer d'utiliser CADisplayLink pour piloter vous-même l'animation. Ce n'est pas trop difficile à configurer puisque vous utilisez une courbe d'animation linéaire de toute façon. Voici une implémentation de base qui peut fonctionner pour vous:

 @property (nonatomic, strong) CADisplayLink *displayLink; @property (nonatomic, assign) CFTimeInterval lastTimerTick; @property (nonatomic, assign) CGFloat animationPointsPerSecond; @property (nonatomic, assign) CGPoint finalContentOffset; -(void)beginAnimation { self.lastTimerTick = 0; self.animationPointsPerSecond = 50; self.finalContentOffset = CGPointMake(..., ...); self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTick:)]; [self.displayLink setFrameInterval:1]; [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; } -(void)endAnimation { [self.displayLink invalidate]; self.displayLink = nil; } -(void)displayLinkTick { if (self.lastTimerTick = 0) { self.lastTimerTick = self.displayLink.timestamp; return; } CFTimeInterval currentTimestamp = self.displayLink.timestamp; CGPoint newContentOffset = self.collectionView.contentOffset; newContentOffset.x += self.animationPointsPerSecond * (currentTimestamp - self.lastTimerTick) self.collectionView.contentOffset = newContentOffset; self.lastTimerTick = currentTimestamp; if (newContentOffset.x >= self.finalContentOffset.x) [self endAnimation]; } 

Je soupçonne que UICollectionView essaye d'améliorer les performances en attendant la fin du parpath avant la mise à jour.

Peut-être pourriez-vous split l'animation en mandrins, même si je ne suis pas sûr de la douceur de cette animation.

Ou peut-être appeler setNeedsDisplay périodiquement pendant le défilement?

Alternativement, peut-être ce rlocation pour UICollectionView sera soit vous voulez ou vous pouvez être modifié pour le faire:

https://github.com/steipete/PSTCollectionView

Voici une mise en œuvre rapide, avec des commentaires expliquant pourquoi cela est nécessaire.

L'idée est la même que dans la réponse de devdavid, seule l'approche de mise en œuvre est différente.

 /* Animated use of `scrollToContentOffset:animated:` doesn't give enough control over the animation duration and curve. Non-animated use of `scrollToContentOffset:animated:` (or contentOffset directly) embedded in an animation block gives more control but interfer with the internal logic of UICollectionView. For example, cells that are not visible for the target contentOffset are removed at the beginning of the animation because from the collection view point of view, the change is not animated and the cells can safely be removed. To fix that, we must control the scroll ourselves. We use CADisplayLink to update the scroll offset step-by-step and render cells if needed alongside. To simplify, we force a linear animation curve, but this can be adapted if needed. */ private var currentScrollDisplayLink: CADisplayLink? private var currentScrollStartTime = Date() private var currentScrollDuration: TimeInterval = 0 private var currentScrollStartContentOffset: CGFloat = 0.0 private var currentScrollEndContentOffset: CGFloat = 0.0 // The curve is hardcoded to linear for simplicity private func beginAnimatedScroll(toContentOffset contentOffset: CGPoint, animationDuration: TimeInterval) { // Cancel previous scroll if needed resetCurrentAnimatedScroll() // Prevent non-animated scroll guard animationDuration != 0 else { logAssertFail("Animation controlled scroll must not be used for non-animated changes") collectionView?.setContentOffset(contentOffset, animated: false) return } // Setup new scroll properties currentScrollStartTime = Date() currentScrollDuration = animationDuration currentScrollStartContentOffset = collectionView?.contentOffset.y ?? 0.0 currentScrollEndContentOffset = contentOffset.y // Start new scroll currentScrollDisplayLink = CADisplayLink(target: self, selector: #selector(handleScrollDisplayLinkTick)) currentScrollDisplayLink?.add(to: RunLoop.current, forMode: .commonModes) } @objc private func handleScrollDisplayLinkTick() { let animationRatio = CGFloat(abs(currentScrollStartTime.timeIntervalSinceNow) / currentScrollDuration) // Animation is finished guard animationRatio < 1 else { endAnimatedScroll() return } // Animation running, update with incremental content offset let deltaContentOffset = animationRatio * (currentScrollEndContentOffset - currentScrollStartContentOffset) let newContentOffset = CGPoint(x: 0.0, y: currentScrollStartContentOffset + deltaContentOffset) collectionView?.setContentOffset(newContentOffset, animated: false) } private func endAnimatedScroll() { let newContentOffset = CGPoint(x: 0.0, y: currentScrollEndContentOffset) collectionView?.setContentOffset(newContentOffset, animated: false) resetCurrentAnimatedScroll() } private func resetCurrentAnimatedScroll() { currentScrollDisplayLink?.invalidate() currentScrollDisplayLink = nil } 

Utilisez :scrollToItemAtIndexPath place:

 [UIView animateWithDuration:duration animations:^{ [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UICollectionViewScrollPositionNone animated:NO]; }];