animationUICollectionView contentOffset不显示不可见的单元格

我正在做一些类似于ticker的function,并使用UICollectionView 。 它最初是一个scrollView,但我们认为一个collectionView会使添加/删除单元格更容易。

我用下面的animationcollectionView:

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

这对于滚动视图来说工作正常,并且animation正在收集视图中发生。 但是,只有在animation结尾可见的单元格才会被渲染。 调整contentOffset不会导致cellForItemAtIndexPath 。 如何在contentOffset更改时获取单元格?

编辑:多一点参考(不知道是否有多大的帮助):

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

你应该简单地添加[self.view layoutIfNeeded]; 在animation块里面,像这样:

 [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]; 

您可以尝试使用CADisplayLink来自己驱动animation。 无论如何,由于您使用的是直线animation曲线,因此这并不难。 这里有一个基本的实现可能适用于你:

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

我怀疑UICollectionView正在尝试通过等待直到滚动结束更新之前提高性能。

也许你可以把animation分成卡盘,虽然我不确定会是多么顺利。

或者也许在滚动期间定期调用setNeedsDisplay?

另外,也许这个replaceUICollectionView将要么需要你需要,否则可以修改,以便这样做:

https://github.com/steipete/PSTCollectionView

这是一个迅速的实现,有解释为什么这是必要的评论。

这个想法和devdavid的答案是一样的,只有实现的方法是不同的。

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

使用:scrollToItemAtIndexPath来代替:

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