在非滚动 UICollectionView 中动态调整 UICollectionViewCell 的大小

Posted

技术标签:

【中文标题】在非滚动 UICollectionView 中动态调整 UICollectionViewCell 的大小【英文标题】:Dynamically resize UICollectionViewCell in non-scrolling UICollectionView 【发布时间】:2015-01-24 23:25:55 【问题描述】:

我在屏幕顶部有一个小的单行水平布局 UICollectionView。它最多可以包含 6 个项目。问题是我希望所有 6 个项目都可见而不滚动(这个集合视图也将用于不允许滚动的 Today 扩展)。我想要做的是稍微减小单元格大小和项目间间距,以允许所有 6 个单元格适合。

基本上我试图避免这种情况:

我已经玩了一段时间了,但我不知道如何处理它。我创建了一个方法,每次在集合视图中添加或删除项目时都会触发它,就在调用[self.collectionview reloadData] 之前。

-(void)setupCollectionViewLayout

     UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)self.buttonBarCollectionView.collectionViewLayout;

        //Figure out if cells are wider than screen
    CGFloat screenwidth = self.view.frame.size.width;

    CGFloat sectionInsetLeft = 10;
    CGFloat sectionInsetRight = 10;
    CGFloat minItemSpacing = flowLayout.minimumInteritemSpacing;
    CGSize  itemsize = CGSizeMake(58,58);
    CGFloat itemsizeWidth = itemsize.width;
    CGFloat totalWidth = sectionInsetLeft + sectionInsetRight +
                        (itemsizeWidth * _armedRemindersArray.count) +
                        (minItemSpacing * (_armedRemindersArray.count -2));


    CGFloat reductionAmount = itemsizeWidth;
    if (totalWidth > screenwidth) 


        while (totalWidth > screenwidth) 
            totalWidth = totalWidth - 1;
             reductionAmount = reductionAmount - 1;
        


        CGSize newCellSize = CGSizeMake(reductionAmount, reductionAmount);
        flowLayout.itemSize = newCellSize;

    

    else flowLayout.itemSize = itemsize;


这是结果。

不完全是我所期待的。它不仅将所有内容都压缩到左侧并添加了第二行,而且我似乎也遇到了单元重用问题。老实说,如果它甚至是一个选项,我只会使用静态单元格,但不幸的是,这似乎是不可能的。

我该怎么办?子类化 UICollectionViewFlowLayout?这不会基本上和我在这里使用内置流布局做同样的事情吗?

编辑: Kujey 的回答肯定更接近我的需要。不过,我仍然有一个细胞重用问题。

【问题讨论】:

【参考方案1】:

Xcode 提供了一个为您的需要而设计的对象。它被称为 UICollectionViewFlowLayout ,您需要做的就是将其子类化并按照您想要的方式放置您的单元格。每次集合视图需要更新布局时,都会调用函数 prepareForLayout。 您需要的代码如下:

#import "CustomLayout.h"

#define MainCell @"MainCell"

@interface CustomLayout ()

@property (nonatomic, strong) NSMutableDictionary *layoutInfo;

@end

@implementation CustomLayout


-(NSMutableDictionary *) layoutInfo

    if (!_layoutInfo) 
        _layoutInfo = [NSMutableDictionary dictionary];
    

    return _layoutInfo;


-(void) prepareLayout

    NSMutableDictionary *cellLayoutInfo = [NSMutableDictionary dictionary];

    NSIndexPath *indexPath;

    CGFloat itemWidth;
    CGFloat itemSpacing;

    CGFloat widthWithoutSpacing = [self collectionViewContentSize].width / ([self.collectionView numberOfItemsInSection:0]);

    if (widthWithoutSpacing > [self collectionViewContentSize].height) 
        itemWidth = [self collectionViewContentSize].height;
        itemSpacing = ([self collectionViewContentSize].width - itemWidth*[self.collectionView numberOfItemsInSection:0])/
        ([self.collectionView numberOfItemsInSection:0]+1);
    
    else 
        itemWidth = widthWithoutSpacing;
        itemSpacing = 0;
    

    CGFloat xPosition = itemSpacing;

    for (NSInteger section = 0; section < [self.collectionView numberOfSections]; section++) 

        for (NSInteger index = 0 ; index < [self.collectionView numberOfItemsInSection:section] ; index++) 

            indexPath = [NSIndexPath indexPathForItem:index inSection:section];
            UICollectionViewLayoutAttributes *itemAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

            CGRect currentFrame=itemAttributes.frame;

            currentFrame.origin.x = xPosition;
            currentFrame.size.width = itemWidth;
            currentFrame.size.height = itemWidth;

            itemAttributes.frame=currentFrame;
            cellLayoutInfo[indexPath] = itemAttributes;

            xPosition += itemWidth + itemSpacing;
        
    

    self.layoutInfo[MainCell] = cellLayoutInfo;



- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

    return YES;



- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

    NSMutableArray *allAttributes = [NSMutableArray arrayWithCapacity:self.layoutInfo.count];

    [self.layoutInfo enumerateKeysAndObjectsUsingBlock:^(NSString *elementIdentifier, NSDictionary *elementsInfo, BOOL *stop) 
        [elementsInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attributes, BOOL *innerStop) 
            if (CGRectIntersectsRect(rect, attributes.frame)) 
                [allAttributes addObject:attributes];
            
        ];
    ];

    return allAttributes;



-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath

    return self.layoutInfo[MainCell][indexPath];



-(CGSize) collectionViewContentSize

    return self.collectionView.frame.size;




@end

如果需要垂直居中,您还可以更改单元格的 y 原点。

【讨论】:

这就像魔术一样。我还不完全确定它是如何工作的,但我会稍微介绍一下。我想为单元格大小设置一个最大值(当只有一个项目时它是巨大的),我似乎仍然有一个单元格重用问题。这让我更接近我需要的地方。谢谢! 什么样的问题?不久前,我有自己的挣扎。或许能帮上忙 当我将更多项目添加到集合视图时,单元格正在叠加。就好像它在计算新大小后创建了一个新的、更小的单元格,但却留下了一个大的、预先计算的单元格。我上面的截图中也发生了同样的事情。 好吧,我刚才为这类问题做了一个回答。您可能以错误的方式自定义您的单元格。这是链接:***.com/questions/23801418/…。你可以像我一样添加任何类型的东西,只要你在 prepareForReuse 中删除它 是的。那成功了。我采取了懒惰的[从超级视图中删除]方式。似乎工作得很好。【参考方案2】:

尝试使用此代码。我得到宽度并使用_armedRemindersArray(我猜你使用这个数组作为项目)。

-(void)setupCollectionViewLayout   
         UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)self.buttonBarCollectionView.collectionViewLayout;

         //Figure out if cells are wider than screen
         CGFloat screenwidth = self.view.frame.size.width;
         CGFloat width = screenwidth - ((sectionInsetLeft + sectionInsetRight) *_armedRemindersArray.count  + minItemSpacing * (_armedRemindersArray.count -2));
         CGSize  itemsize = CGSizeMake(width,width);
         flowLayout.itemSize = itemsize;

【讨论】:

我遇到了与 Armin M 的回答相同的问题。 Itemize 被计算为溢出值。【参考方案3】:

我不知道你为什么要先设置itemsize,然后再减少它。我认为你应该反过来做:

-(void)setupCollectionViewLayout

     UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)self.buttonBarCollectionView.collectionViewLayout;

    CGFloat sectionInsetLeft = 10;
    CGFloat sectionInsetRight = 10;
    CGFloat minItemSpacing = flowLayout.minimumInteritemSpacing;

    // Find the appropriate item width (<= 58)
    CGFloat screenwidth = self.view.frame.size.width;
    CGFloat itemsizeWidth = (screenwidth - sectionInsetLeft - sectionInsetRight - (minItemSpacing * (_armedRemindersArray.count -2))) / _armedRemindersArray.count
    itemsizeWidth = itemsizeWidth > 58 ? 58 : itemsizeWidth;

    flowLayout.itemSize = CGSizeMake(itemsizeWidth, itemsizeWidth);

这行得通吗?如果没有,您能否包含更多您的代码?

【讨论】:

没用。由于某种原因, itemsizeWidth 以溢出值结束。 Kujey 的回答似乎有效。谢谢! 会不会是因为 _armedRemindersArray.count 是 0 ?无论如何,很高兴你找到了答案。

以上是关于在非滚动 UICollectionView 中动态调整 UICollectionViewCell 的大小的主要内容,如果未能解决你的问题,请参考以下文章

UICollectionView 滚动 SWIFT 滞后/缓慢

滚动时 Uicollectionview 不更新标题

Swift:滚动时 UICollectionView 标题消失

UICollectionVIew:在视图滚动时为单元格设置动画

动态修复屏幕中的 UICollectionView 单元格

使用自动布局中的固有大小动态调整 UICollectionView 的大小