我正在研究我已在Android上开发的应用的iOS版本。此应用程序具有以下2列自定义(固定宽度但可变高度)单元格:
在Android版本中实现这一点非常简单,因为Google为其StaggeredGridLayoutManager
提供了RecyclerView
。您可以指定列数和滚动方向。
默认UICollectionView
布局UICollectionViewFlowLayout
不允许我正在寻找的交错布局,因此我必须实现自定义布局。我观看了2个关于这个话题的WWDC视频(What's New in Table and Collection Views和Advanced User Interfaces with Collection Views),我或多或少都知道应该如何实现它。
第1步。首先计算布局的近似值。
第2步。然后使用自动布局创建单元格并调整其大小。
第3步。然后控制器会通知单元格大小,以便更新布局。
我在尝试编写这些步骤时遇到了疑问。我找到了一个tutorial来解释创建具有交错列的自定义布局,但它不使用autolayout来获取单元格的大小。这让我有以下问题:
在第2步中,我如何以及何时可以获得单元格大小?
在第3步中,我如何以及何时通知更改的布局?
答案 0 :(得分:5)
我想指出的是,正如您所提到的,RayWenderlich PinInterest Layout正是帮助您实现此布局的教程。
回答你的问题 - 关于教程:
在第2步中,我如何以及何时可以获得单元格大小?
要获取单元格高度,已实现在自定义prepareLayout
的{{1}}方法中调用的委托方法。此方法称为一次(或两次,我只是尝试使用UICollectionViewLayout
语句运行它,我有两个调用)。 print
的要点是初始化单元格的prepareLayout
属性,换句话说,提供每个单元格的确切大小。我们知道宽度是常数,只有frame
正在发生变化,因此在height
的这一行中:
prepareLayout
我们从let cellHeight = delegate.collectionView(collectionView!,
heightForItemAtIndexPath: indexPath, withWidth: width)
中实现的委托方法获取单元格的高度。对于我们想要在UICollectionViewController
中显示的所有单元格,都会发生这种情况。获取并修改每个单元格的高度后,我们缓存结果,以便稍后检查。
然后,为了collectionView
获取屏幕上每个单元格的大小,它需要做的就是查询缓存中的信息。这是在自定义collectionView
类的layoutAttributesForElementsInRect
方法中完成的。
此方法由UICollectionViewLayout
自动调用。当UICollectionViewController
需要进入屏幕的单元格的布局信息(例如,作为滚动的结果,或首次加载时),您将从您在{中填充的缓存中返回属性{1}}。
总结您的问题:在第2步中,我如何以及何时可以获得单元格大小?
答案:每个单元格大小都是在自定义UICollectionViewController
的{{1}}方法中获得的,并且会在prepareLayout
的生命周期的早期计算出来。
在第3步中,我如何以及何时通知更改的布局?
请注意,本教程不考虑在运行时添加的新单元格:
注意:由于在集合视图的布局无效时调用prepareLayout(),因此在典型实现中可能需要在此处重新计算属性。例如,UICollectionView的边界可能会更改 - 例如方向更改时 - 或者可以在集合中添加或删除项目。这些案例超出了本教程的范围,但在非平凡的实现中了解它们非常重要。
就像他写的那样,这可能是你需要的一个非常重要的实现。但是,如果您的数据集较小(或用于测试目的),则可能会采用一种简单(非常低效)的实现。当您因屏幕旋转或添加/删除单元格而需要使布局无效时,可以在自定义prepareLayout
中清除缓存以强制UICollectionViewFlowLayout
重新初始化布局属性。
例如,当您必须在collectionView上调用UICollectionView
时,也要调用自定义布局类,以删除缓存:
UICollectionViewFlowLayout
答案 1 :(得分:2)
我意识到这不是一个完整的答案,但有关您的步骤2和3的一些指示可以在UICollectionViewLayout
的子类注释中找到。
我认为你已经隐藏了UICollectionViewFlowLayout
,因为我认为这是一个很好的起点,可以调整布局以获得你想要的交错外观。
对于第2步,layoutAttributesForElementsInRect(_:)
应提供自定义单元格的布局属性。
对于第3步,您的布局将使用更改的单元格大小调用shouldInvalidateLayoutForPreferredLayoutAttributes(_:withOriginalAttributes:)
。
答案 2 :(得分:1)
在步骤2中,我如何以及何时可以获得单元格大小? 您需要在 prepareLayout()方法中计算每个单元格的高度。每个单元格的计算结果应分配给 UICollectionViewLayoutAttributes 变量,然后将其放入集合NSDictionary中,其中key为NSIndexPath(每个单元格),值为UICollectionViewLayoutAttributes变量。
示例:
- (void)prepareLayout {
[_layoutMap removeAllObjects];
_totalItemsInSection = [self.collectionView numberOfItemsInSection:0];
_columnsYoffset = [self initialDataForColumnsOffsetY];
if (_totalItemsInSection > 0 && self.totalColumns > 0) {
[self calculateItemsSize];
NSInteger itemIndex = 0;
CGFloat contentSizeHeight = 0;
while (itemIndex < _totalItemsInSection) {
NSIndexPath *targetIndexPath = [NSIndexPath indexPathForItem:itemIndex inSection:0];
NSInteger columnIndex = [self columnIndexForItemAtIndexPath:targetIndexPath];
// you need to implement this method and perform your calculations
CGRect attributeRect = [self calculateItemFrameAtIndexPath:targetIndexPath];
UICollectionViewLayoutAttributes *targetLayoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:targetIndexPath];
targetLayoutAttributes.frame = attributeRect;
contentSizeHeight = MAX(CGRectGetMaxY(attributeRect), contentSizeHeight);
_columnsYoffset[columnIndex] = @(CGRectGetMaxY(attributeRect) + self.interItemsSpacing);
_layoutMap[targetIndexPath] = targetLayoutAttributes;
itemIndex += 1;
}
_contentSize = CGSizeMake(self.collectionView.bounds.size.width - self.contentInsets.left - self.contentInsets.right,
contentSizeHeight);
}
}
不要忘记实施以下方法:
- (NSArray <UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray<UICollectionViewLayoutAttributes *> *layoutAttributesArray = [NSMutableArray new];
for (UICollectionViewLayoutAttributes *layoutAttributes in _layoutMap.allValues) {
if (CGRectIntersectsRect(layoutAttributes.frame, rect)) {
[layoutAttributesArray addObject:layoutAttributes];
}
}
return layoutAttributesArray;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
return _layoutMap[indexPath];
}
一旦调用reloadData()mehtod或invalidateLayout(),就会触发这些方法。
在第3步中,我如何以及何时通知更改的布局? 只需调用self.collectionView.collectionViewLayout.invalidateLayout()并再次调用prepareLayout()方法,即可重新计算所需的所有参数。
您可以在此处找到有关自定义UICollectionViewLayout的完整教程:https://octodev.net/custom-collectionviewlayout/
教程包含两种语言的实现:Swift和Objective-C。 非常乐意回答你的所有问题。
答案 3 :(得分:0)
“单元格大小”由布局子类中的UICollectionViewLayoutAttribute定义,这意味着您可以在每次有机会触摸它时对其进行修改。您可以将每个属性的大小设置为您想要的大小。
例如,您可以在layoutAttributesOfElementsInRect(:)中执行此操作,计算正确的大小并配置所有属性,然后再将它们传递给collectionView。您也可以在layoutAttributeOfItemAtIndexPath(:)中执行此操作,在创建每个属性时进行计算。
此外,考虑通过数据源提供所需的大小,以便每个属性都可以轻松地获得其索引的大小。
如果您想要单元格大小来布局单元格中的子视图,请在collectionView委托方法中执行:collectionView:ItemAtIndexPath:
希望得到这个帮助。