You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 16:07:05