我试图实现的应用程序的一个功能是使用户能够将此UICollectionView的布局从网格视图更改为表视图.
我花了很多时间尝试完善这个工作,让它工作,我设法让它工作.
但是有一些问题.两个布局之间的转换看起来不太好,有时在切换视图之间会中断,而我的应用程序仍然处于意外状态.只有当用户非常快速地切换网格和表格视图(按changeLayoutButton)时才会发生.
所以,显然有一些问题,我觉得代码有点..脆弱.我也需要解决上述问题.
我会开始,我实现了这个观点.
履行
由于我需要两个不同的单元格(grideCell和tableViewCell)来显示不同的东西 – 我决定更好地子类化UICollectionViewFlowLayout,因为它执行了所有我需要的 – 我需要做的只是改变单元格的大小.
考虑到这一点,我创建了两个类,它们分类为UICollectionViewFlowLayout
这就是两个班的外观:
BBTradeFeedTableViewLayout.m
#import "BBTradeFeedTableViewLayout.h" @implementation BBTradeFeedTableViewLayout -(id)init { self = [super init]; if (self){ self.itemSize = CGSizeMake(320,80); self.minimumLineSpacing = 0.1f; } return self; } @end
BBTradeFeedGridViewLayout.m
#import "BBTradeFeedGridViewLayout.h" @implementation BBTradeFeedGridViewLayout -(id)init { self = [super init]; if (self){ self.itemSize = CGSizeMake(159,200); self.minimumInteritemSpacing = 2; self.minimumLineSpacing = 3; } return self; } @end
很简单,你可以看到 – 只是改变单元格大小.
然后在我的viewControllerA类中,我实现了这样的UICollectionView:
创建属性:
@property (strong,nonatomic) BBTradeFeedTableViewLayout *tableViewLayout; @property (strong,nonatomic) BBTradeFeedGridViewLayout *grideLayout;
在viewDidLoad
/* Register the cells that need to be loaded for the layouts used */ [self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:@"BBItemTableViewCell" bundle:nil] forCellWithReuseIdentifier:@"TableItemCell"]; [self.tradeFeedCollectionView registerNib:[UINib nibWithNibName:@"BBItemGridViewCell" bundle:nil] forCellWithReuseIdentifier:@"GridItemCell"];
用户点击按钮在布局之间切换:
-(void)changeViewLayoutButtonPressed
我使用BOOL来确定哪个布局当前处于活动状态,并且基于我使用此代码进行切换:
[self.collectionView performBatchUpdates:^{ [self.collectionView.collectionViewLayout invalidateLayout]; [self.collectionView setCollectionViewLayout:self.grideLayout animated:YES]; } completion:^(BOOL finished) { }];
在cellForItemAtIndexPath中
我确定我应该使用哪个单元格(grid或tableView)和加载数据 – 代码如下所示:
if (self.gridLayoutActive == NO){ self.switchToTableLayout = NO; BBItemTableViewCell *tableItemCell = [collectionView dequeueReusableCellWithReuseIdentifier:tableCellIdentifier forIndexPath:indexPath]; if ([self.searchArray count] > 0){ self.switchToTableLayout = NO; tableItemCell.gridView = NO; tableItemCell.backgroundColor = [UIColor whiteColor]; tableItemCell.item = self.searchArray[indexPath.row]; } return tableItemCell; }else { BBItemTableViewCell *gridItemCell= [collectionView dequeueReusableCellWithReuseIdentifier:gridCellIdentifier forIndexPath:indexPath]; if ([self.searchArray count] > 0){ self.switchToTableLayout = YES; gridItemCell.gridView = YES; gridItemCell.backgroundColor = [UIColor whiteColor]; gridItemCell.item = self.searchArray[indexPath.row]; } return gridItemCell; }
最后在两个单元格类 – 我只是使用数据来设置我需要的图像/文本.
在网格单元格中,图像越大,我删除不想要的文本,这是使用两个单元格的主要原因.
我真的很感激一些帮助,如何解决我目前的问题,并改善这种观点,看起来更流畅,更少..在UI中的bug.我要去的样子就像eBay iOS应用程序 – 它们在三种不同的视图之间切换.我只需要在两个不同的视图之间切换.
解决方法
>你无法控制动画的时间和性质.
>虽然布局中的单元格的框架从一个值到另一个值是动画的,但单元本身(特别是如果您使用两个单独的单元格)似乎不对动画的每个步骤执行内部布局,因此似乎从每个单元格中的一个布局“下滑”到下一个布局 – 您的网格单元在表格大小上看起来错误,反之亦然.
有很多不同的解决方案.很难推荐任何具体的内容,但没有看到您的单元格的内容,但我已经取得了以下成功:
>使用如here所示的技术控制动画.您还可以查看Facebook Pop来更好地控制转换,但我没有仔细研究.
>对于这两个布局使用相同的单元格.在layoutSubviews中,计算从一个布局到另一个布局的过渡距离,并将其用于淡出或未使用的元素,并为其他元素计算好的过渡帧.这可以防止从一个单元格类别到另一个单元格类型的抖动开关.
依靠调整面具或Autolayout的大小,这是更难的工作,但这是额外的工作,使事情看起来不错.
对于用户可以在布局之间切换太快的问题,只需在转换开始时禁用该按钮,并在完成后重新启用它.
作为一个更实际的例子,这里是从上面链接的应用程序的布局变化(一些省略)的示例.注意,在转换发生时,交互被禁用,我正在使用从上面链接的项目中的转换布局,并且有一个完成处理程序:
-(void)toggleLayout:(UIButton*)sender { [[UIApplication sharedApplication] beginIgnoringInteractionEvents]; HMNNewsLayout newLayoutType = self.layoutType == HMNNewsLayoutTable ? HMNNewsLayoutGrid : HMNNewsLayoutTable; UICollectionViewLayout *newLayout = [HMNNewsCollectionViewController collectionViewLayoutForType:newLayoutType]; HMNTransitionLayout *transitionLayout = (HMNTransitionLayout *)[self.collectionView transitionToCollectionViewLayout:newLayout duration:0.5 easing:QuarticEaseInOut completion:^(BOOL completed,BOOL finish) { [[NSUserDefaults standardUserDefaults] setInteger:newLayoutType forKey:HMNNewsLayoutTypeKey]; self.layoutType = newLayoutType; sender.selected = !sender.selected; for (HMNNewsCell *cell in self.collectionView.visibleCells) { cell.layoutType = newLayoutType; } [[UIApplication sharedApplication] endIgnoringInteractionEvents]; }]; [transitionLayout setUpdateLayoutAttributes:^UICollectionViewLayoutAttributes *(UICollectionViewLayoutAttributes *layoutAttributes,UICollectionViewLayoutAttributes *fromAttributes,UICollectionViewLayoutAttributes *toAttributes,CGFloat progress) { HMNTransitionLayoutAttributes *attributes = (HMNTransitionLayoutAttributes *)layoutAttributes; attributes.progress = progress; attributes.destinationLayoutType = newLayoutType; return attributes; }]; }
在单元格中,这是任一布局的相同单元格,我有一个图像视图和一个标签容器.标签容器保留所有标签,并使用自动布局将其放在内部.每个布局中的图像视图和标签容器都有不变的框架变量.
过渡布局中的布局属性是一个自定义子类,其中包含在上述更新布局属性块中设置的转换进度属性.这使用applyLayoutAttributes方法(其他一些代码省略)传递到单元格中:
-(void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes { self.transitionProgress = 0; if ([layoutAttributes isKindOfClass:[HMNTransitionLayoutAttributes class]]) { HMNTransitionLayoutAttributes *attributes = (HMNTransitionLayoutAttributes *)layoutAttributes; self.transitionProgress = attributes.progress; } [super applyLayoutAttributes:layoutAttributes]; }
layout在细胞子类中的浏览在图像和标签的两个帧之间进行内插的艰巨工作,如果正在进行转换:
-(void)layoutSubviews { [super layoutSubviews]; if (!self.transitionProgress) { switch (self.layoutType) { case HMNNewsLayoutTable: self.imageView.frame = imageViewTableFrame; self.labelContainer.frame = labelContainerTableFrame; break; case HMNNewsLayoutGrid: self.imageView.frame = imageViewGridFrame; self.labelContainer.frame = self.originalGridLabelFrame; break; } } else { CGRect fromImageFrame,toImageFrame,fromLabelFrame,toLabelFrame; if (self.layoutType == HMNNewsLayoutTable) { fromImageFrame = imageViewTableFrame; toImageFrame = imageViewGridFrame; fromLabelFrame = labelContainerTableFrame; toLabelFrame = self.originalGridLabelFrame; } else { fromImageFrame = imageViewGridFrame; toImageFrame = imageViewTableFrame; fromLabelFrame = self.originalGridLabelFrame; toLabelFrame = labelContainerTableFrame; } CGFloat from = 1.0 - self.transitionProgress; CGFloat to = self.transitionProgress; self.imageView.frame = (CGRect) { .origin.x = from * fromImageFrame.origin.x + to * toImageFrame.origin.x,.origin.y = from * fromImageFrame.origin.y + to * toImageFrame.origin.y,.size.width = from * fromImageFrame.size.width + to * toImageFrame.size.width,.size.height = from * fromImageFrame.size.height + to * toImageFrame.size.height }; self.labelContainer.frame = (CGRect) { .origin.x = from * fromLabelFrame.origin.x + to * toLabelFrame.origin.x,.origin.y = from * fromLabelFrame.origin.y + to * toLabelFrame.origin.y,.size.width = from * fromLabelFrame.size.width + to * toLabelFrame.size.width,.size.height = from * fromLabelFrame.size.height + to * toLabelFrame.size.height }; } self.headlineLabel.preferredMaxLayoutWidth = self.labelContainer.frame.size.width; }
就是这样.基本上,您需要一种告诉单元格的过程有多远,您需要布局转换库(或者像我所说的Facebook pop可能会这样做),然后您需要确保您获得不错的值用于在两者之间转换时进行布局.