在UICollectionViewLayout的prepareLayout中获取Cell引发栈溢出,求正确方法
嗨,这个问题我太熟悉了!你遇到的栈溢出(EXC_BAD_ACCESS)本质是循环调用导致的无限递归——大概率你是在sizeForItemAtIndexPath:这类布局计算方法里直接调用cellForItemAtIndexPath:或者dequeueReusableCell...,而这两个方法又会触发cell的布局逻辑,反过来再次调用sizeForItemAtIndexPath:,反复循环直到栈被撑爆。
下面给你几个安全可靠的实现方式:
1. 手动实例化Cell(最推荐)
绕开CollectionView的回调循环,直接从XIB或代码创建一个独立的Cell实例,模拟真实数据后计算尺寸:
// 从XIB加载Cell(如果是纯代码创建,直接初始化即可) NSArray *nibItems = [[NSBundle mainBundle] loadNibNamed:@"YourItemCell" owner:nil options:nil]; YourItemCell *measureCell = [nibItems firstObject]; // 给Cell设置要展示的数据,还原真实布局状态 measureCell.itemData = yourDataSourceArray[indexPath.item]; // 强制Cell完成布局,确保子视图尺寸都已计算 [measureCell setNeedsLayout]; [measureCell layoutIfNeeded]; // 计算适配的尺寸(根据需求选UILayoutFittingCompressedSize或ExpandedSize) CGSize targetSize = [measureCell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
这种方式完全独立于CollectionView的回调链,不会触发循环。
2. 利用布局属性获取尺寸
如果你用的是自定义UICollectionViewLayout,或者已经提前计算好尺寸并存在布局属性里,可以直接通过布局属性获取:
UICollectionViewLayoutAttributes *attrs = [self.collectionView layoutAttributesForItemAtIndexPath:indexPath]; CGSize cellSize = attrs.size;
这也是你提到在layoutAttributesForElementsInRect:里调用没问题的原因——这个阶段布局逻辑已经走完,不会再触发sizeForItemAtIndexPath:的循环。
关键避坑点
绝对不要在sizeForItemAtIndexPath:、collectionView:layout:insetForSectionAtIndex:这类布局计算方法里,直接调用collectionView.dataSource的cell创建方法,或者通过CollectionView dequeue Cell!这几乎必然会引发循环递归,导致栈溢出。
另外你提到的控制台po能正常输出Cell,是因为调试命令是直接调用方法,没有触发后续的布局回调链;而layoutAttributesForElementsInRect:处于布局流程的后期,不会反向触发尺寸计算,所以也不会出问题。
内容的提问来源于stack exchange,提问作者nrudnyk

