iOS开发之UI篇(11)—— UICollectionView

版本
Xcode 9.1

一、布局结构

与UITableView很类似,UICollectionView也由组头、组尾、cell来组成:

UICollectionView布局结构(纵向)

其中,①②③④分别是cell在CollectionView里面的上下左右边界缩进,⑤是cell与cell之间的列间间隔,⑥是cell与cell之间的行间间隔。

二、UICollectionView的创建

1、示例代码

#define     VIEW_WIDTH      [UIScreen mainScreen].bounds.size.width
#define     VIEW_HEIGHT     [UIScreen mainScreen].bounds.size.height

#import "ViewController.h"

@interface ViewController () <UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, UICollectionViewDataSource>

@property (nonatomic, strong)   UICollectionView *collectionView;

@end


@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // 添加collectionView
    [self.view addSubview:self.collectionView];
}


#pragma mark - UICollectionViewDelegateFlowLayout

// 返回Header的尺寸大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
    
    return CGSizeMake(VIEW_WIDTH, 33);
}


// 返回cell的尺寸大小
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    return CGSizeMake(50, 50);      // 让每个cell尺寸都不一样
}


// 返回Footer的尺寸大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section {

    return CGSizeMake(VIEW_WIDTH, 33);
}


// 返回cell之间行间隙
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {

    return 10;
}


// 返回cell之间列间隙
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {

    return 20;
}


// 设置上左下右边界缩进
-(UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {

    return UIEdgeInsetsMake(20, 20, 20, 20);
}


#pragma mark - UICollectionViewDataSource

// 返回Section个数
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    
    return 2;
}


// 返回cell个数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {

    return 10;
}


// 返回cell内容
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

    // 创建cell (重用)
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyCell" forIndexPath:indexPath];

    // 设置cell
    if (indexPath.section == 0) {
        cell.backgroundColor = [UIColor purpleColor];
    }else {
        cell.backgroundColor = [UIColor darkGrayColor];
    }
    
    UILabel *label = [[UILabel alloc] initWithFrame:cell.bounds];
    label.textAlignment = NSTextAlignmentCenter;
    label.text = @"Cell";
    label.textColor = [UIColor whiteColor];
    [cell addSubview:label];

    return cell;
}


// 返回Header/Footer内容
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    
    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {          // Header视图
        // 从复用队列中获取HooterView
        UICollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"MyHeader" forIndexPath:indexPath];
        // 设置HooterView
        headerView.backgroundColor = [UIColor redColor];
        UILabel *label = [[UILabel alloc] initWithFrame:headerView.bounds];
        label.textAlignment = NSTextAlignmentCenter;
        label.text = @"我是Header";
        label.textColor = [UIColor whiteColor];
        [headerView addSubview:label];
        // 返回HooterView
        return headerView;
        
    }else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) {    // Footer视图
        // 从复用队列中获取FooterView
        UICollectionReusableView *footerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"MyFooter" forIndexPath:indexPath];
        // 设置FooterView
        footerView.backgroundColor = [UIColor blueColor];
        UILabel *label = [[UILabel alloc] initWithFrame:footerView.bounds];
        label.textAlignment = NSTextAlignmentCenter;
        label.text = @"我是Footer";
        label.textColor = [UIColor whiteColor];
        [footerView addSubview:label];
        // 返回FooterView
        return footerView;
    }

    return nil;
}


#pragma mark - UICollectionViewDelegate 

// 选中某个cell
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {

    NSLog(@"选中cell: %ld", indexPath.row);
}


#pragma mark - 懒加载

- (UICollectionView *)collectionView {
    
    if (_collectionView == nil) {
        // 创建FlowLayout
        UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
        // 垂直方向滑动
        flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
        // 创建collectionView
        CGRect frame = CGRectMake(0, 0, VIEW_WIDTH, VIEW_HEIGHT);
        _collectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:flowLayout];
        // 设置代理
        _collectionView.delegate = self;
        _collectionView.dataSource = self;
        // 其他属性
        _collectionView.backgroundColor = [UIColor clearColor];
        _collectionView.showsVerticalScrollIndicator = NO;          // 隐藏垂直方向滚动条
        // 注册cell
        [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"MyCell"];
        // 注册Header
        [_collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"MyHeader"];
        // 注册Footer
        [_collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"MyFooter"];
    }
    
    return _collectionView;
}


@end

2、自定义cell(3种创建方式)

a、纯代码创建cell

先在创建collectionView的时候注册cell,再在cellForItemAtIndexPath方法中复用cell。

// 注册cell
[_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"MyCell"];

// 然后在cellForItemAtIndexPath方法中重用
// 创建cell (重用)
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyCell" forIndexPath:indexPath];
注:在UITableView中可以不提前注册cell,然后cellForRowAtIndexPath方法中判断cell为空时新建一个cell
if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
}
但是在UICollectionView中,一定要先注册cell才能使用(UICollectionViewCell是没有init方法的)。

b、使用XIB创建cell

创建XIB cell
设置identifier

然后先注册:

// 注册XIB cell
[_collectionView registerNib:[UINib nibWithNibName:@"MyXIBCell" bundle:nil] forCellWithReuseIdentifier:@"MyXIBCell"];

再使用:

// 创建cell (重用)
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyXIBCell" forIndexPath:indexPath];

c、使用storyboard创建cell

使用storyboard无需注册cell,因在创建的时候cell已经被关联到相应的collectionView里了,系统在创建collectionView的时候已经帮我们注册cell了,我们只需要设置storyboard里cell的Identifier,然后直接复用就好啦

storyboard创建cell
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyStoryboardCell" forIndexPath:indexPath];

三、流布局、数据源和代理方法

1、流布局 UICollectionViewDelegateFlowLayout

// cell行间的间距最小距离
@property (nonatomic) CGFloat minimumLineSpacing;
// cell列间的间距最小距离
@property (nonatomic) CGFloat minimumInteritemSpacing;
// 每个item(cell)的大小
@property (nonatomic) CGSize itemSize;
// 每个Item(cell)的预估大小
@property (nonatomic) CGSize estimatedItemSize NS_AVAILABLE_IOS(8_0); 
// 滚动方向(默认垂直方向)
@property (nonatomic) UICollectionViewScrollDirection scrollDirection; 
// 组头视图大小
@property (nonatomic) CGSize headerReferenceSize;
// 组尾视图大小
@property (nonatomic) CGSize footerReferenceSize;
// cells的上下左右边界缩进
@property (nonatomic) UIEdgeInsets sectionInset;
// 组头视图是否固定在屏幕上边
@property (nonatomic) BOOL sectionHeadersPinToVisibleBounds NS_AVAILABLE_IOS(9_0);
// 组尾视图是否固定在屏幕下边
@property (nonatomic) BOOL sectionFootersPinToVisibleBounds NS_AVAILABLE_IOS(9_0);

/* 以下均为代理方法 */ 
// 动态设置每个Item(cell)的尺寸大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
// 动态设置cells的上下左右边界缩进
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
// 动态设置cell行间的间距最小距离
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;
// 动态设置cell列间的间距最小距离
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
// 动态设置组头视图大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
// 动态设置组尾视图大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;

2、数据源 UICollectionViewDataSource

@required 必须实现

// 每组中cell的个数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;
// 返回cell (cell必须使用dequeueReusableCellWithReuseIdentifier:forIndexPath:方法复用)
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;

@optional 可选实现

// 设置组数(默认1组)
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;
// 返回组头/组尾视图(必须使用dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:方法复用)
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
// 设置某个cell是否可以移动
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0);
// 移动cell的时候,会调用这个方法
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS(9_0);

3、代理方法 UICollectionViewDelegate

// 设置某个cell是否可以高亮
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
// 某cell高亮时调用此方法
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath;
// 某cell结束高亮时调用此方法
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath;
// 设置某个cell是否可以选中
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;
// 设置某个cell是否可以取消选中
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath; 
// 选中某个cell时调用此方法
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
// 取消选中某个cell时调用此方法
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath;

// 将要显示某个cell时调用此方法
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0);
// 将要显示某个组头/组尾视图时调用此方法
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0);
// 已经显示某个cell时调用此方法
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;
// 已经显示某个组头/组尾视图时调用此方法
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;

// 设置是否展示长按cell菜单
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath;
// 设置要展示的菜单选项
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender;
// 点击菜单按钮后调用此方法(只有copy、cut和paste三个方法可用)
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender;

// collectionView重新布局时调用此方法
- (nonnull UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout;

四、遇到的问题

1. 当UICollectionViewCell只有一个时,系统自动设置成居中对齐

如图:


居中对齐

一般的,我们想让所有section中的cell左对齐,如下:


左对齐

有两种方法:
法一:
可以使用私有方法,使用运行时机制:

#import <objc/message.h>

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // cell只剩下一个时自动左对齐
    SEL sel = NSSelectorFromString(@"_setRowAlignmentsOptions:");
    if ([self.collectionView.collectionViewLayout respondsToSelector:sel]) {
        ((void(*)(id,SEL,NSDictionary*))objc_msgSend)(self.collectionView.collectionViewLayout, sel,
                                                      @{@"UIFlowLayoutCommonRowHorizontalAlignmentKey":@(NSTextAlignmentLeft),
                                                      @"UIFlowLayoutLastRowHorizontalAlignmentKey" : @(NSTextAlignmentLeft),
                                                      @"UIFlowLayoutRowVerticalAlignmentKey" : @(NSTextAlignmentCenter)});
    }
}

用此私有方法目前可以上架审核通过。

法二:
强制cell居左。
原理是判断section中剩下最后一个cell时,设置右边距足够大,使得左边显示部分只剩下一个cell的宽度:

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {

    if (section < self.groups.count) {
        if (self.groups[section].count == 1) {
            return UIEdgeInsetsMake(0, 0, 0, collectionView.frame.size.width-60);   // 60是我的固定cell宽
        }
    }
    
    return UIEdgeInsetsMake(0, 0, 0, 0);
}

这种方法计算麻烦,但没有使用苹果的私有方法,不会有上架被拒风险。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 翻译自“Collection View Programming Guide for iOS” 0 关于iOS集合视...
    lakerszhy阅读 4,147评论 1 22
  • 最近将 UICollectionView 进行了一个全面的学习及总结,参考了网上大量的文章,把官方文档进行了大概翻...
    varlarzh阅读 22,303评论 3 93
  • 这两天这三个字在热搜榜上高居不下,再一次把这个话题推上了风口浪尖上是台湾作家琼瑶阿姨,她希望在她生命的最后一刻她的...
    炙热的烤猪排阅读 261评论 0 0
  • 中元节也就是人们常说的鬼节! 鬼节!一听到这个名字都有点恐怖!但其中的文学又有多少人了解!传说:这一天阎王会打开地...
    就是要写点东西阅读 415评论 0 1
  • 实时查询 1.标准API 正如我们在LiveQuery协议中讨论的,我们保持一个与Parse LiveQuery服...
    xiangdong_9013阅读 708评论 0 0

友情链接更多精彩内容