Ios 如何开发自定义UICollectionViewLayout,该布局具有交错的列和自调整大小的单元格?
我正在开发我已经在Android上开发的应用程序的iOS版本。此应用程序具有以下2列自调整大小(固定宽度但可变高度)单元格网格: 在Android版本中实现这一点很容易,因为Google为其Ios 如何开发自定义UICollectionViewLayout,该布局具有交错的列和自调整大小的单元格?,ios,swift,Ios,Swift,我正在开发我已经在Android上开发的应用程序的iOS版本。此应用程序具有以下2列自调整大小(固定宽度但可变高度)单元格网格: 在Android版本中实现这一点很容易,因为Google为其RecyclerView提供了一个StaggedGridLayoutManager。指定列数和滚动方向,即可完成 默认的UICollectionViewlayoutUICollectionViewFlowLayout不允许我正在寻找的交错布局,因此我必须实现自定义布局。我看了两个WWDC视频,讨论了这个主题
RecyclerView
提供了一个StaggedGridLayoutManager
。指定列数和滚动方向,即可完成
默认的UICollectionView
layoutUICollectionViewFlowLayout
不允许我正在寻找的交错布局,因此我必须实现自定义布局。我看了两个WWDC视频,讨论了这个主题(和),我或多或少对如何实现它有了想法
步骤1。首先计算布局的近似值
第2步。然后使用autolayout创建单元格并调整大小
第3步。然后,控制器将单元格大小通知用户,以便更新布局
当我试图编写这些步骤时,我产生了怀疑。我找到了一个解释如何创建具有交错列的自定义布局的方法,但它不使用autolayout来获取单元格的大小。这给我留下了以下问题:
在步骤2中,如何以及何时获得单元格大小?
在步骤3中,我如何以及何时才能通知布局更改?我意识到这不是一个完整的答案,但是关于步骤2和步骤3的一些提示可以在
UICollectionViewLayout
的子类化注释中找到
我假设您已经对UICollectionViewFlowLayout
进行了子类化,因为我相信这是调整布局以获得所需交错外观的一个良好起点
对于步骤2layouttributesforementsinrect(:)
应提供自大小单元格的布局属性
对于步骤3,您的布局将使用更改的单元格大小进行调用。我想指出,正如您所提到的,这正是帮助您实现此布局的教程 回答您的问题-关于本教程: 在步骤2中,如何以及何时获得单元格大小? 为了获得单元格高度,实现了一个委托方法,该方法在自定义
UICollectionViewLayout
的prepareLayout
方法中调用。这个方法被称为一次(或者两次,我只是试图用print
语句运行它,我得到了两次调用)。prepareLayout
的要点是初始化单元格的frame
属性,换句话说,提供每个单元格的确切大小。我们知道宽度是恒定的,只有高度
在变化,因此在prepareLayout
的这一行中:
let cellHeight = delegate.collectionView(collectionView!,
heightForItemAtIndexPath: indexPath, withWidth: width)
我们通过在UICollectionViewController
中实现的委托方法获得单元格的高度。这适用于我们希望在collectionView
中显示的所有单元格。在获得并修改每个单元的高度后,我们缓存结果,以便稍后检查
之后,collectionView
要获得屏幕上每个单元格的大小,只需查询缓存中的信息。这是在自定义UICollectionViewLayout
类的layouttributesforementsinrect
方法中完成的
此方法由UICollectionViewController
自动调用。当UICollectionViewController
需要屏幕上单元格的布局信息时(例如,由于滚动或第一次加载),您可以从缓存中返回在prepareLayout
中填充的属性
作为您问题的结论:在步骤2中,如何以及何时获得单元格大小?
回答:每个单元格大小都是在自定义UICollectionViewFlowLayout
的prepareLayout
方法中获得的,并在UICollectionView
的生命周期的早期进行计算
在步骤3中,如何以及何时通知布局更改?
请注意,本教程不考虑在运行时添加的新单元格:
注意:由于每当集合视图的布局无效时都会调用prepareLayout(),因此在典型的实现中,在许多情况下可能需要在此处重新计算属性。例如,UICollectionView的边界可能会更改(例如方向更改时),或者项目可能会从集合中添加或删除。这些情况超出了本教程的范围,但在一个非常重要的实现中注意它们是很重要的
正如他所写的,这是一个您可能需要的非常重要的实现。然而,如果您的数据集很小(或出于测试目的),您可能会采用一种微不足道的(非常低效的)实现。当由于屏幕旋转或添加/删除单元格而需要使布局无效时,可以清除自定义UICollectionViewFlowLayout
中的缓存,以强制prepareLayout
重新初始化布局属性
例如,当您必须在collectionView上调用reloadData
时,还要调用自定义布局类来删除缓存:
cache.removeAll()
“单元格大小”由布局子类中的UICollectionViewLayoutAttribute定义,这意味着您可以在每次有机会触摸它们时对其进行修改。您可以根据需要设置每个属性的大小
例如,您可以在LayoutAttributesOfelementsRect(:)中执行此操作,计算正确的大小并配置所有属性,然后将它们传递给collectionView。你也可以
- (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];
}