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

版本
Xcode 9.1

##一、布局结构

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

![UICollectionView布局结构(纵向)](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy84OTAwNzk1LWM3ZmE5ZDU3ODIzOGI0ZjQucG5n?x-oss-process=image/format,png)

其中,①②③④分别是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

![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy84OTAwNzk1LWEyMDBiZGIwNDRmNTZiNzAuanBlZw?x-oss-process=image/format,png) 创建XIB cell
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy84OTAwNzk1LWQ3OTQ2OWM1ODc2MTRiM2QuanBlZw?x-oss-process=image/format,png) 设置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,然后直接复用就好啦

![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy84OTAwNzk1LTY2MzJmNDNlZTY3NjU1MzYuanBlZw?x-oss-process=image/format,png) 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);
}

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值