UICollectionView 布局自定义

Posted

技术标签:

【中文标题】UICollectionView 布局自定义【英文标题】:UICollectionView Layout customization 【发布时间】:2014-01-06 09:59:47 【问题描述】:

我有很多部分和项目。当我触摸单元格中的按钮时,它会在 1 个单元格中创建包含 4 个项目的单元格(如展开的表格),再次触摸单元格消失。

我找到了WaterfallCollectionView,在那里我可以更改项目的高度。

实现这一目标的最佳方法是什么???

结构如下:

 -----      -----
|    +|    |    +|   and other rows
|     |    |     |
 -----      -----

当我触摸我的按钮 (+) 时,它应该是这样的:

 -----      -----     -----
|    -|    |__|__|   |    +|
|     |    |  |  |   |     |
 -----      -----     -----

在 1 个单元格内创建 4 个 UIImageView 的新单元格(如果 1 个单元格中有 4 个元素,则创建更多单元格)。如果 1 个元素创建此单元格,但图像将位于左上角。 我的信息应该在单元格中的哪个位置(例如展开的表格)

创建不同类型的单元格更好吗?

【问题讨论】:

如果单元格如您所展示的那样简单并且不需要不同的单元格,那么您可以继续使用expand table @cjd 我需要不同的单元格。 我希望collapseclick 能帮到你。 【参考方案1】:

我尝试了通过更改自身大小来扩展UICollectionViewCell 的方法。当您点击一个单元格时弹出的新“单元格”并不是真正的新单元格,而是原始单元格的子视图。本质上它的工作原理是这样的:

    使用UICollectionViewUICollectionViewFlowLayoutUICollectionView 的委托符合 UICollectionViewDelegateFlowLayout 并实现 -collectionView:layout:sizeForItemAtIndexPath: 以识别唯一的单元格大小。 UICollectionView 的委托实现 -collectionView:didSelectCellAtIndexPath:,它调整单元格的大小以显示或隐藏看起来像新单元格的子视图。 表示单元格内容的自定义对象和自定义UICollectionViewCell 子类用于更轻松地执行和跟踪扩展。

我创建了一个example project here,它有一个单元格,可以展开以显示数字的除数。它看起来像这样:

该项目相当粗糙且未注释,并且在没有动画的情况下进行过渡,但是如果您觉得这种方法很有趣并且无法遵循代码,我可以将其清理一下。

这是代码转储...

CollectionViewController.h

#import <UIKit/UIKit.h>

@interface CollectionViewController : UIViewController

@end

CollectionViewController.m

#import "CollectionViewController.h"
#import "CellDataItem.h"
#import "CollectionViewCell.h"

@interface CollectionViewController () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>

@property (strong, nonatomic) UICollectionView *collectionView;
@property (strong, nonatomic) NSMutableArray *cellData;

@end

NSString *const kCollectionViewCellIdentifier = @"Cell";

@implementation CollectionViewController

- (NSMutableArray *)cellData

    if (!_cellData) 
        NSInteger countValues = 20;
        _cellData = [[NSMutableArray alloc] initWithCapacity:countValues];
        for (NSInteger i = 0; i < countValues; i++) 
            CellDataItem *item = [[CellDataItem alloc] init];
            item.number = @(arc4random() % 100);
            [_cellData addObject:item];
        
    
    return _cellData;


- (void)viewDidLoad

    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor grayColor];

    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    layout.itemSize = [CollectionViewCell sizeWithDataItem:nil];
    layout.minimumInteritemSpacing = [CollectionViewCell margin];
    layout.minimumLineSpacing = layout.minimumInteritemSpacing;
    layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero
                                         collectionViewLayout:layout];
    _collectionView.backgroundColor = [UIColor darkGrayColor];
    _collectionView.dataSource = self;
    _collectionView.delegate = self;
    [self.view addSubview:_collectionView];

    [_collectionView registerClass:[CollectionViewCell class] forCellWithReuseIdentifier:kCollectionViewCellIdentifier];


- (void)viewDidLayoutSubviews

    [super viewDidLayoutSubviews];

    UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
    self.collectionView.frame = CGRectMake(0.0f, 0.0f, self.view.bounds.size.width, layout.itemSize.height);


- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView

    return 1;


- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section

    return self.cellData.count;


- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

    CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCollectionViewCellIdentifier
                                                         forIndexPath:indexPath];
    cell.dataItem = [self.cellData objectAtIndex:indexPath.row];
    return cell;


- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath

    return [CollectionViewCell sizeWithDataItem:[self.cellData objectAtIndex:indexPath.row]];


- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath

    CollectionViewCell *cell = (CollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
    [cell toggleExpansion];
    [collectionView reloadData];


@end

#import <Foundation/Foundation.h>

@interface CellDataItem : NSObject

@property (strong, nonatomic) NSNumber *number;
@property (nonatomic, readonly) NSArray *divisors;
@property (nonatomic, getter = isExpanded) BOOL expanded;

@end

CellDataItem.m

#import "CellDataItem.h"

@interface CellDataItem ()

@property (strong, nonatomic) NSArray *divisors;

@end

@implementation CellDataItem

+ (NSArray *)divisorsForNumber:(NSNumber *)number

    NSMutableArray *divisors = [NSMutableArray arrayWithObjects:@(1), number, nil];
    float root = pow(number.doubleValue, 0.5);
    if (root == roundf(root)) 
        [divisors addObject:[NSNumber numberWithInteger:(NSInteger)root]];
    

    NSInteger maxDivisor = (NSInteger)root;
    for (NSInteger divisor = 2; divisor < maxDivisor; divisor++) 
        float quotient = number.floatValue / (float)divisor;
        if (quotient == roundf(quotient)) 
            [divisors addObject:[NSNumber numberWithInteger:divisor]];
            [divisors addObject:[NSNumber numberWithInteger:(NSInteger)quotient]];
        
    
    return [divisors sortedArrayUsingSelector:@selector(compare:)];


- (void)setNumber:(NSNumber *)number

    if (_number == number) 
        return;
    
    _number = number;
    self.divisors = [self.class divisorsForNumber:_number];


@end

CollectionViewCell.h

#import <UIKit/UIKit.h>

@class CellDataItem;
@interface CollectionViewCell : UICollectionViewCell

+ (CGFloat)margin;
+ (CGSize)sizeWithDataItem:(CellDataItem *)dataItem;

@property (strong, nonatomic) CellDataItem *dataItem;

- (void)toggleExpansion;

@end

@interface ChildView : UIView

- (UILabel *)labelAtIndex:(NSInteger)index;
- (void)clearLabels;

@end

CollectionViewCell.m

#import "CollectionViewCell.h"
#import <QuartzCore/QuartzCore.h>
#import "CellDataItem.h"

@interface CollectionViewCell ()

@property (strong, nonatomic) NSMutableArray *childViews;
@property (strong, nonatomic) UILabel *numberLabel;

@end

NSInteger const kCollectionViewCellSplitCount = 4;
CGFloat const kCollectionViewCellMargin = 20.0f;
CGSize const kCollectionViewCellDefaultSize = 200.0f, 200.0f;

@implementation CollectionViewCell

+ (CGFloat)margin

    return kCollectionViewCellMargin;


+ (CGSize)sizeWithDataItem:(CellDataItem *)dataItem

    if (dataItem && dataItem.isExpanded) 
        CGSize size = kCollectionViewCellDefaultSize;
        NSInteger childViewsRequired = [self childViewsRequiredForDataItem:dataItem];
        size.width += childViewsRequired * ([self margin] + size.width);
        return size;
     else 
        return kCollectionViewCellDefaultSize;
    


+ (NSInteger)childViewsRequiredForDataItem:(CellDataItem *)dataItem

    return (NSInteger)ceilf((float)dataItem.divisors.count / (float)kCollectionViewCellSplitCount);


- (id)initWithFrame:(CGRect)frame

    self = [super initWithFrame:frame];
    if (self) 
        // Initialization code
        _numberLabel = [[UILabel alloc] init];
        _numberLabel.textAlignment = NSTextAlignmentCenter;
        _numberLabel.layer.borderColor = [UIColor blackColor].CGColor;
        _numberLabel.layer.borderWidth = 1.0f;
        _numberLabel.backgroundColor = [UIColor whiteColor];
        [self.contentView addSubview:_numberLabel];
    
    return self;


- (void)setDataItem:(CellDataItem *)dataItem

    if (_dataItem == dataItem) 
        return;
    

    _dataItem = dataItem;
    self.numberLabel.text = [NSString stringWithFormat:@"%i", dataItem.number.integerValue];

    if (!dataItem.expanded) 
        [self collapse];
     else if (dataItem.expanded) 
        [self expand];
    


- (void)collapse

    for (ChildView *view in self.childViews) 
        view.hidden = YES;
    


- (void)expand

    NSInteger childViewsRequired = [self.class childViewsRequiredForDataItem:self.dataItem];
    while (self.childViews.count < childViewsRequired) 
        ChildView *childView = [[ChildView alloc] init];
        [self.childViews addObject:childView];
        [self.contentView addSubview:childView];
    

    NSInteger index = 0;
    for (ChildView *view in self.childViews) 
        view.hidden = !(index < childViewsRequired);
        if (!view.hidden) 
            [view clearLabels];
        
        index++;
    

    for (NSInteger i = 0; i < self.dataItem.divisors.count; i++) 
        NSInteger labelsPerChild = 4;
        NSInteger childIndex = i / labelsPerChild;
        NSInteger labelIndex = i % labelsPerChild;
        [[[self.childViews objectAtIndex:childIndex] labelAtIndex:labelIndex] setText:[NSString stringWithFormat:@"%i", [[self.dataItem.divisors objectAtIndex:i] integerValue]]];
    


- (void)layoutSubviews

    [super layoutSubviews];

    CGFloat const unitWidth = kCollectionViewCellDefaultSize.width;
    CGFloat const unitHeight = kCollectionViewCellDefaultSize.height;
    CGFloat const margin = [self.class margin];
    self.numberLabel.frame = CGRectMake(0.0f, 0.0f, unitWidth, unitHeight);

    for (NSInteger i = 0; i < self.childViews.count; i++) 
        ChildView *view = [self.childViews objectAtIndex:i];
        view.frame = CGRectMake((i + 1) * (margin + unitWidth), 0.0f, unitWidth, unitHeight);
    


- (NSMutableArray *)childViews

    if (!_childViews) 
        _childViews = [[NSMutableArray alloc] init];
    
    return _childViews;


- (void)toggleExpansion

    self.dataItem.expanded = !self.dataItem.isExpanded;
    if (self.dataItem.isExpanded) 
        [self expand];
     else 
        [self collapse];
    


@end

@interface ChildView ()

@property (strong, nonatomic) NSMutableArray *labels;

@end

@implementation ChildView

- (id)init

    if ((self = [super init])) 
        self.backgroundColor = [UIColor lightGrayColor];
    
    return self;


- (UILabel *)labelAtIndex:(NSInteger)index

    if (!self.labels) 
        self.labels = [NSMutableArray array];
    

    while (self.labels.count <= index) 
        UILabel *label = [[UILabel alloc] init];
        label.textAlignment = NSTextAlignmentCenter;
        label.layer.borderColor = [UIColor blackColor].CGColor;
        label.layer.borderWidth = 1.0f;
        [self.labels addObject:label];
        [self addSubview:label];
    

    return [self.labels objectAtIndex:index];


- (void)clearLabels

    for (UILabel *label in self.labels) 
        label.text = nil;
    


- (void)layoutSubviews

    [super layoutSubviews];

    CGFloat labelWidth = self.bounds.size.width * 0.5f;
    CGFloat labelHeight = self.bounds.size.height * 0.5f;
    for (NSInteger i = 0; i < self.labels.count; i++) 
        UILabel *label = [self.labels objectAtIndex:i];
        NSInteger x = i % 2;
        NSInteger y = i / 2;
        label.frame = CGRectMake(x * labelWidth, y * labelHeight, labelWidth, labelHeight);
    


@end

【讨论】:

【参考方案2】:

如果您想要可扩展的单元格,您必须通过在集合视图上调用 insertItemsAtIndexPaths: 将新单元格插入到集合视图中。

这不是微不足道的,因为您必须移动所有索引路径,更新部分中的项目数以反映新添加的单元格等。如果您正在寻找简单的东西,那么瀑布示例很好。如果您想要一个更复杂/可配置的解决方案,那么您必须摇滚自己的自定义布局。我就是这么做的。

【讨论】:

以上是关于UICollectionView 布局自定义的主要内容,如果未能解决你的问题,请参考以下文章

iOS UICollectionView自定义流水布局(一)

UICollectionView 中的自定义布局

具有自定义布局的 UICollectionView 变为空白

Swift 中的 UICollectionView 自定义布局

UICollectionView 布局在滚动时表现异常

UICollectionView 自定义布局(---照片浏览器)