自定义UICollectionViewFlowLayout实现半圆布局问题求助
UICollectionView半圆布局无法滚动问题排查与解决
我希望用UICollectionView展示卡片,通过自定义UICollectionViewFlowLayout实现半圆布局,但目前无法切换卡片,打印self.collectionView.contentOffset.x结果始终为0,请问问题出在哪里?
自定义Layout代码
@interface ArcCollectionViewFlowLayout() @property (nonatomic, assign) NSInteger currentPage; @property (nonatomic, assign) CGSize topItemSize; @end static const int kFlowLayoutVisibleItemCount = 7; @implementation ArcCollectionViewFlowLayout - (void)prepareLayout { [super prepareLayout]; } - (CGSize)collectionViewContentSize { return self.collectionView.frame.size; } - (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { NSInteger itemsCount = [self.collectionView numberOfItemsInSection:0]; if (itemsCount <= 0) { return nil; } NSInteger minVisibleIndex = MAX(self.currentPage, 0); NSInteger contentOffset = (int)self.collectionView.contentOffset.x; NSInteger collectionBounds = (int)self.collectionView.bounds.size.width; CGFloat offset = contentOffset % collectionBounds; CGFloat offsetProgress = offset / collectionBounds*1.0f; CGFloat offsetAngle = offsetProgress / self.itemSize.width * 30; NSInteger maxVisibleIndex = MAX(MIN(itemsCount - 1, self.currentPage + kFlowLayoutVisibleItemCount), minVisibleIndex); NSMutableArray *attributeArray = [NSMutableArray array]; for (NSInteger i = minVisibleIndex; i<= maxVisibleIndex; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; UICollectionViewLayoutAttributes *attributes = [[self layoutAttributesForItemAtIndexPath:indexPath] copy]; attributes.center = CGPointMake(collectionBounds/2.0, self.collectionView.bounds.size.height / 2.0); if (i == minVisibleIndex) { attributes.zIndex = 1000; [self rotate:attributes offsetAngle:-90+offsetAngle]; } else if (i == minVisibleIndex + 1) { attributes.zIndex = 1000 + 1; [self rotate:attributes offsetAngle:-60+offsetAngle]; } else if (i == minVisibleIndex + 2) { attributes.zIndex = 1000 + 2; [self rotate:attributes offsetAngle:-30+offsetAngle]; } else if (i == minVisibleIndex + 3) { attributes.zIndex = 1000 + 3; [self rotate:attributes offsetAngle:0+offsetAngle]; } else if (i == minVisibleIndex + 4) { attributes.zIndex = 1000 + 2; [self rotate:attributes offsetAngle:30+offsetAngle]; } else if (i == minVisibleIndex + 5) { attributes.zIndex = 1000 + 1; [self rotate:attributes offsetAngle:60+offsetAngle]; } else if (i == minVisibleIndex + 6) { attributes.zIndex = 1000; [self rotate:attributes offsetAngle:90+offsetAngle]; } attributes.size = self.topItemSize; [attributeArray addObject:attributes]; } return attributeArray; } - (void)rotate:(UICollectionViewLayoutAttributes *)attributes offsetAngle:(CGFloat)offsetAngle { CGAffineTransform t0 = CGAffineTransformMakeTranslation(0, -attributes.size.height / 2.0); CGAffineTransform t1 = CGAffineTransformMakeRotation(angleToRadian(offsetAngle)); CGAffineTransform t2 = CGAffineTransformMakeTranslation(0, attributes.size.height / 2.0); CGAffineTransform t = CGAffineTransformConcat(CGAffineTransformConcat(t0, t1), t2); attributes.transform = t; } - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return YES; } - (NSInteger)currentPage { return MAX(floor(self.collectionView.contentOffset.x / 100), 0); } - (CGSize)topItemSize { return CGSizeMake(100, 160); }
问题根源与修复方案
1. 内容区域大小错误(核心问题)
collectionViewContentSize直接返回CollectionView的frame尺寸,导致内容区域和可视区域完全重合,CollectionView没有可滚动的空间,因此contentOffset.x始终为0。
修复代码:
- (CGSize)collectionViewContentSize { NSInteger itemsCount = [self.collectionView numberOfItemsInSection:0]; if (itemsCount == 0) { return self.collectionView.frame.size; } // 计算可滚动的总宽度:可视区域宽度 + (总item数-1)*单步滚动宽度(这里用卡片宽度作为步长) CGFloat contentWidth = self.collectionView.bounds.size.width + (itemsCount - 1) * self.topItemSize.width; return CGSizeMake(contentWidth, self.collectionView.bounds.size.height); }
2. 未初始化itemSize
prepareLayout中未设置self.itemSize,导致self.itemSize默认是CGSizeZero,后续计算offsetAngle时会触发除以0的异常,同时破坏布局逻辑。
修复代码:
- (void)prepareLayout { [super prepareLayout]; self.itemSize = self.topItemSize; // 初始化item尺寸为自定义卡片大小 }
3. currentPage边界判断缺失
原currentPage计算未限制最大值,当滚动到最后一个item后,继续滚动会导致索引越界,需要补充边界判断:
- (NSInteger)currentPage { NSInteger itemsCount = [self.collectionView numberOfItemsInSection:0]; if (itemsCount == 0) return 0; CGFloat page = floor(self.collectionView.contentOffset.x / self.topItemSize.width); return MAX(MIN(page, itemsCount - 1), 0); // 限制在有效item索引范围内 }
4. 缺失角度转换函数
代码中使用了angleToRadian但未实现,需要补充该工具函数:
static CGFloat angleToRadian(CGFloat angle) { return angle * M_PI / 180.0; }
内容的提问来源于stack exchange,提问作者xcgg
相关产品推荐
相关产品推荐

