在UICollectionView上动态设置布局会导致无法解释的contentOffset更改
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在UICollectionView上动态设置布局会导致无法解释的contentOffset更改相关的知识,希望对你有一定的参考价值。
根据Apple的文档(并在WWDC 2012上吹捧),可以动态设置UICollectionView
上的布局,甚至可以为更改设置动画:
通常在创建集合视图时指定布局对象,但也可以动态更改集合视图的布局。布局对象存储在collectionViewLayout属性中。设置此属性会立即直接更新布局,而无需动画更改。如果要为更改设置动画,则必须调用setCollectionViewLayout:animated:方法。
然而,在实践中,我发现UICollectionView
对contentOffset
进行了莫名其妙甚至无效的更改,导致细胞移动不正确,使得该功能几乎无法使用。为了说明这个问题,我将以下示例代码放在一起,这些代码可以附加到放入故事板的默认集合视图控制器中:
#import <UIKit/UIKit.h>
@interface MyCollectionViewController : UICollectionViewController
@end
@implementation MyCollectionViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"CELL"];
self.collectionView.collectionViewLayout = [[UICollectionViewFlowLayout alloc] init];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"CELL" forIndexPath:indexPath];
cell.backgroundColor = [UIColor whiteColor];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"contentOffset=(%f, %f)", self.collectionView.contentOffset.x, self.collectionView.contentOffset.y);
[self.collectionView setCollectionViewLayout:[[UICollectionViewFlowLayout alloc] init] animated:YES];
NSLog(@"contentOffset=(%f, %f)", self.collectionView.contentOffset.x, self.collectionView.contentOffset.y);
}
@end
控制器在UICollectionViewFlowLayout
中设置默认的viewDidLoad
并在屏幕上显示单个单元格。选择单元格后,控制器会创建另一个默认的UICollectionViewFlowLayout
,并使用animated:YES
标志将其设置在集合视图上。预期的行为是细胞不移动。然而,实际的行为是单元格在屏幕外滚动,此时甚至无法在屏幕上滚动单元格。
查看控制台日志显示contentOffset已经莫名其妙地改变了(在我的项目中,从(0,0)到(0,205))。我发布了solution for the non-animated case(i.e. animated:NO
)的解决方案,但由于我需要动画,我很有兴趣知道是否有人有动画案例的解决方案或解决方法。
作为旁注,我测试了自定义布局并获得了相同的行为。
我已经把头发拉过来好几天了,并找到了解决我的情况可能会有所帮助的方法。在我的情况下,我有一个折叠的照片布局,就像在iPad上的照片应用程序。它显示相册顶部的相册,当您点击相册时,它会扩展照片。所以我所拥有的是两个单独的UICollectionViewLayouts并且正在用[self.collectionView setCollectionViewLayout:myLayout animated:YES]
在它们之间切换我在动画之前跳过的细胞确实存在问题并且意识到它是contentOffset
。我用contentOffset
尝试了一切,但在动画期间它仍然跳跃。上面的泰勒的解决方案有效,但它仍然在搞乱动画。
然后我注意到它只在屏幕上有几张专辑时才会出现,不足以填满屏幕。我的布局按建议覆盖了-(CGSize)collectionViewContentSize
。当只有少数专辑时,集合视图的内容大小小于视图内容大小。当我在集合布局之间切换时,这会导致跳转。
所以我在我的布局上设置了一个名为minHeight的属性,并将其设置为集合视图父级的高度。然后我在-(CGSize)collectionViewContentSize
返回之前检查高度我确保高度> =最小高度。
不是一个真正的解决方案,但它现在工作正常。我会尝试将集合视图的contentSize
设置为至少包含视图的长度。
编辑:如果从UICollectionViewFlowLayout继承,Manicaesar添加了一个简单的解决方法:
-(CGSize)collectionViewContentSize { //Workaround
CGSize superSize = [super collectionViewContentSize];
CGRect frame = self.collectionView.frame;
return CGSizeMake(fmaxf(superSize.width, CGRectGetWidth(frame)), fmaxf(superSize.height, CGRectGetHeight(frame)));
}
这最终对我有用(Swift 3)
self.collectionView.collectionViewLayout = UICollectionViewFlowLayout()
self.collectionView.setContentOffset(CGPoint(x: 0, y: -118), animated: true)
UICollectionViewLayout
包含可覆盖的方法targetContentOffsetForProposedContentOffset:
,它允许您在更改布局期间提供适当的内容偏移,这将正确动画。这在ios 7.0及更高版本中可用
这个问题也困扰我,它似乎是转换代码中的一个错误。据我所知,它试图关注最靠近转换前视图布局中心的单元格。但是,如果在转换前的视图中心没有碰巧出现一个单元格,那么它仍然会尝试将单元格转换为转移后的中心位置。如果将alwaysBounceVertical / Horizontal设置为YES,使用单个单元格加载视图,然后执行布局转换,则非常清楚。
通过在触发布局更新后明确告知集合关注特定单元格(在此示例中为第一个单元格可见单元格),我能够解决这个问题。
[self.collectionView setCollectionViewLayout:[self generateNextLayout] animated:YES];
// scroll to the first visible cell
if ( 0 < self.collectionView.indexPathsForVisibleItems.count ) {
NSIndexPath *firstVisibleIdx = [[self.collectionView indexPathsForVisibleItems] objectAtIndex:0];
[self.collectionView scrollToItemAtIndexPath:firstVisibleIdx atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
}
对我自己的问题迟到了。
TLLayoutTransitioning库通过重新分配iOS7s交互式转换API来执行非交互式布局到布局转换,从而为这个问题提供了一个很好的解决方案。它有效地提供了setCollectionViewLayout
的替代方案,解决了内容偏移问题并添加了几个功能:
- 动画时长
- 30多条宽松曲线(由Warren Moore的AHEasing library提供)
- 多种内容偏移模式
自定义缓动曲线可以定义为AHEasingFunction
函数。可以使用Minimal,Center,Top,Left,Bottom或Right放置选项,根据一个或多个索引路径指定最终内容偏移量。
要了解我的意思,请尝试在示例工作区中运行Resize demo并使用选项。
用法是这样的。首先,配置视图控制器以返回TLTransitionLayout
的实例:
- (UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout
{
return [[TLTransitionLayout alloc] initWithCurrentLayout:fromLayout nextLayout:toLayout];
}
然后,不要调用setCollectionViewLayout
,而是调用transitionToCollectionViewLayout:toLayout
类别中定义的UICollectionView-TLLayoutTransitioning
:
UICollectionViewLayout *toLayout = ...; // the layout to transition to
CGFloat duration = 2.0;
AHEasingFunction easing = QuarticEaseInOut;
TLTransitionLayout *layout = (TLTransitionLayout *)[collectionView transitionToCollectionViewLayout:toLayout duration:duration easing:easing completion:nil];
此调用启动交互式转换,并在内部启动CADisplayLink
回调,该回调以指定的持续时间和缓动函数驱动转换进度。
下一步是指定最终内容偏移量。您可以指定任意值,但toContentOffsetForLayout
中定义的UICollectionView-TLLayoutTransitioning
方法提供了一种优雅的方法来计算相对于一个或多个索引路径的内容偏移量。例如,为了使特定单元格尽可能靠近集合视图的中心,请在transitionToCollectionViewLayout
之后立即进行以下调用:
NSIndexPath *indexPath = ...; // the index path of the cell to center
TLTransitionLayoutIndexPathPlacement placement = TLTransitionLayoutIndexPathPlacementCenter;
CGPoint toOffset = [collectionView toContentOffsetForLayout:layout indexPaths:@[indexPath] placement:placement];
layout.toContentOffset = toOffset;
简单。
在同一动画块中为新布局和collectionView的contentOffset设置动画。
[UIView animateWithDuration:0.3 animations:^{
[self.collectionView setCollectionViewLayout:self.someLayout animated:YES completion:nil];
[self.collectionView setContentOffset:CGPointMake(0, -64)];
} completion:nil];
它将保持self.collectionView.contentOffset
不变。
迅捷3。
UICollectionViewLayout子类。 cdemiris99建议的方式:
override public func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
return collectionView?.contentOffset ?? CGPoint.zero
}
我使用自己的自定义layout自己测试了它
如果您只是在寻找从布局转换时不会更改的内容偏移量,您可以创建自定义布局并覆盖几个方法来跟踪旧的contentOffset并重用它:
@interface CustomLayout ()
@property (nonatomic) NSValue *previousContentOffset;
@end
@implementation CustomLayout
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
{
CGPoint previousContentOffset = [self.previousContentOffset CGPointValue];
CGPoint superContentOffset = [super targetContentOffsetForProposedContentOffset:proposedContentOffset];
return self.previousContentOffset != nil ? previousContentOffset : superContentOffset ;
}
- (void)prepareForTransitionFromLayout:(UICollectionViewLayout *)oldLayout
{
self.previousContentOffset = [NSValue valueWithCGPoint:self.collectionView.contentOffset];
return [super prepareForTransitionFromLayout:oldLayout];
}
- (void)finalizeLayoutTransition
{
self.previousContentOffset = nil;
return [super finalizeLayoutTransition];
}
@end
所有这一切都是在prepareForTransitionFromLayout
中的布局转换之前保存先前的内容偏移,覆盖targetContentOffsetForProposedContentOffset
中的新内容偏移,并在finalizeLayoutTransition
中清除它。非常直截了当
如果它有助于增加体验:无论我的内容大小,我是否设置了内容插入或任何其他明显因素,我都会持续遇到此问题。所以我的解决方案有点激烈。首先,我将UICollectionView子类化,并添加到对抗不适当的内容偏移设置:
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
if(_declineContentOffset) return;
[super setContentOffset:contentOffset];
}
- (void)setContentOffset:(CGPoint)contentOffset
{
if(_declineContentOffset) return;
[super setContentOffset:contentOffset];
}
- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout animated:(BOOL)animated
{
_declineContentOffset ++;
[super setCollectionViewLayout:layout animated:animated];
_declineContentOffset --;
}
- (void)setContentSize:(CGSize)contentSize
{
_declineContentOffset ++;
[super setContentSize:contentSize];
_declineContentOffset --;
}
我并不为此感到自豪,但唯一可行的解决方案似乎完全是拒绝集合视图的任何尝
以上是关于在UICollectionView上动态设置布局会导致无法解释的contentOffset更改的主要内容,如果未能解决你的问题,请参考以下文章
嵌入在 UITableViewCell 中的动态、自定义布局 UICollectionView