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
的方法。当您点击一个单元格时弹出的新“单元格”并不是真正的新单元格,而是原始单元格的子视图。本质上它的工作原理是这样的:
-
使用
UICollectionView
和UICollectionViewFlowLayout
。
UICollectionView
的委托符合 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 变为空白