iOS UI控件详解—「UICollectionView综合视图」

Write in the first【写在最前】


UITableView 熟悉吧, UICollectionView 必须熟悉吧。
WWDC2012中的Introducing Collection Views,苹果首次介绍了UICollectionView,类似UITableView的用法使人很容易接受,但强大的自定义布局,又使其相较于UITableView有了选择它的更多理由,UITableView中的表格只支持单排列表,没有办法支持网格列表模式,CollectionView有着灵活的布局特性,这一点充分说明我们在学会UITableView的基础上,再去学习推敲CollectionView的必要性。

本篇文章主要从【UICollectionView 系统文件注解】学习总结。
在「时间 & 知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出,以提高文章质量。

目录:

  1. UICollectionView概念
  2. UICollectionView基本组成
  3. UICollectionView层次结构
    1.UICollectionView 继承于 UIScrollView
    2.UICollectionViewDataSource数据源
    3.UICollectionViewDelegate代理
    4.UICollectionViewLayout自定义布局对象
    5.UICollectionViewFlowLayout布局对象(默认)
    6.UICollectionViewCell样式
    7.UICollectionViewLayoutAttributes布局属性
  4. UICollectionView与UITableView比较
  5. UICollectionView使用说明
  6. UICollectionView基本使用
  7. 自定义FlowLayout:水平滚动相册
  8. UICollectionView效果图
  9. 自定义FlowLayout:瀑布流
  10. UICollectionView.h 属性&方法

UICollectionView概念


本着好好学习,了解权威的目的,我们还是主动看官网的说明。

 iOS 6.0+

上图释义:管理有序的数据项集合和使用自定制的布局。

通俗点就是:UICollectionView 是一种新的数据展示方式,简单来说可以把他理解成多列的UITableView,可以做九宫格布局的一种view

UICollectionView基本组成


标准的UICollectionView 包含三个部分.png

注解:如上图:你看到的就是一个最简单的UICollectionView,它包含:CellsSupplementary ViewsDecoration Views

  • Cells:用于展示内容的主体,cell的尺寸和内容可以各不相同。

  • Supplementary Views:追加视图,类似于UITableView每个SecitonHeader View 或者Footer View,用来标记SectionView

  • Decoration Views:装饰视图,完全跟数据没有关系的视图,负责给cell 或者supplementary Views添加辅助视图用的,灵活性较强。

  • 不管多么复杂的UIcollectionView都是由着三个部件组成的。

UICollectionView层次结构


UICollectionView 层次结构

注解

1、UICollectionView 继承于 UIScrollView
      
      
1
      
      
NS_CLASS_AVAILABLE_IOS( 6_0) @interface : UIScrollView
2、UICollectionViewDataSource:主要管理视图数据源方面,告诉view要显示些什么东西以及如何显示它们。
  • @required(必须)
      
      
1
2
3
4
5
6
7
8
9
      
      
@protocol UICollectionViewDataSource <NSObject>
@required
- ( NSInteger)collectionView:( UICollectionView *)collectionView numberOfItemsInSection:( NSInteger)section;
// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath:
/* (必须)设置每个区中【item的内容】,类似于UITableViewCell的设置 */
- (__kindof UICollectionViewCell *)collectionView:( UICollectionView *)collectionView cellForItemAtIndexPath:( NSIndexPath *)indexPath;
  • @optional(可选)
      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
      
      
@optional(可选)
/* (可选)设置容器视图有多少组Section,系统默认返回值为1 */
- ( NSInteger)numberOfSectionsInCollectionView:( UICollectionView *)collectionView;
// The view that is returned must be retrieved from a call to -dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:
// 补充视图,这里可以充当区的头和尾,我们自己不实现的话,系统默认返回值为nil
/* (可选)返回顶部视图和底部视图,通过kind参数分辨是设置顶部还是底部(补充视图) */
- ( UICollectionReusableView *)collectionView:( UICollectionView *)collectionView viewForSupplementaryElementOfKind:( NSString *)kind atIndexPath:( NSIndexPath *)indexPath;
/** (可选)询问是否指定的单元格项目是否可以移动到集合视图中的另一个位置,默认返回值为NO */
- ( BOOL)collectionView:( UICollectionView *)collectionView canMoveItemAtIndexPath:( NSIndexPath *)indexPath NS_AVAILABLE_IOS( 9_0);
/** (可选)将指定的单元格项目从一个位置移动到集合视图中的另一个位置 */
- ( void)collectionView:( UICollectionView *)collectionView moveItemAtIndexPath:( NSIndexPath *)sourceIndexPath toIndexPath:( NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS( 9_0);
3、UICollectionViewDelegate:主要管理于用户交互方面,提供一些样式的小细节。
  • @optional(可选)
      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
      
      
@protocol UICollectionViewDelegate <UIScrollViewDelegate>
@optional
// Methods for notification of selection/deselection and highlight/unhighlight events.
// The sequence of calls leading to selection from a user touch is:
// (when the touch begins)
// 1. -collectionView:shouldHighlightItemAtIndexPath:
// 2. -collectionView:didHighlightItemAtIndexPath:
//
// (when the touch lifts)
// 3. -collectionView:shouldSelectItemAtIndexPath: or -collectionView:shouldDeselectItemAtIndexPath:
// 4. -collectionView:didSelectItemAtIndexPath: or -collectionView:didDeselectItemAtIndexPath:
// 5. -collectionView:didUnhighlightItemAtIndexPath:
/** 下面是和高亮有关的方法: */
// cell点击时是否高亮,点击cell时的样式和点击后cell的样式
- ( BOOL)collectionView:( UICollectionView *)collectionView shouldHighlightItemAtIndexPath:( NSIndexPath *)indexPath;
// 手指按下高亮
- ( void)collectionView:( UICollectionView *)collectionView didHighlightItemAtIndexPath:( NSIndexPath *)indexPath;
// 手指松开取消高亮
- ( void)collectionView:( UICollectionView *)collectionView didUnhighlightItemAtIndexPath:( NSIndexPath *)indexPath;
/** 当前item是否可以点击 */
- ( BOOL)collectionView:( UICollectionView *)collectionView shouldSelectItemAtIndexPath:( NSIndexPath *)indexPath;
/** 当前item是否取消点击 */
- ( BOOL)collectionView:( UICollectionView *)collectionView shouldDeselectItemAtIndexPath:( NSIndexPath *)indexPath; // called when the user taps on an already-selected item in multi-select mode
  • 事件的处理顺序如下:

    • 1、手指按下:shouldHighlightItemAtIndexPath (如果返回YES则向下执行,否则执行到这里为止)。

    • 2、didHighlightItemAtIndexPath (高亮)。

    • 3、手指松开:didUnhighlightItemAtIndexPath (取消高亮)。

    • 4、shouldSelectItemAtIndexPath (如果返回YES则向下执行,否则执行到这里为止)。

    • 5、didSelectItemAtIndexPath (执行选择事件)。

  • 选中 和 取消选中 item时 ,会触发的方法

      
      
1
2
3
4
      
      
/* 选中item时 ,会触发的方法 */
- ( void)collectionView:( UICollectionView *)collectionView didSelectItemAtIndexPath:( NSIndexPath *)indexPath;
/* 取消选中item时 ,会触发的方法 */
- ( void)collectionView:( UICollectionView *)collectionView didDeselectItemAtIndexPath:( NSIndexPath *)indexPath;
  • 补充视图(头部或尾部视图),显示 和 移除
      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
      
      
/**
这两个方法分别是指定indexPath的cell将要显示出的时候调用,
和指定indexPath的头部或尾部视图view将要显示出来的时候调用
*/
- ( 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);
/**
这两个方法分别是指定indexPath的cell将要从collectionView中移除的的时候调用,
和指定indexPath的头部或尾部视图view将要collectionView中移除的时候调用
*/
- ( void)collectionView:( UICollectionView *)collectionView didEndDisplayingCell:( UICollectionViewCell *)cell forItemAtIndexPath:( NSIndexPath *)indexPath;
- ( void)collectionView:( UICollectionView *)collectionView didEndDisplayingSupplementaryView:( UICollectionReusableView *)view forElementOfKind:( NSString *)elementKind atIndexPath:( NSIndexPath *)indexPath;
  • 长按某item,弹出copy(复制)paste(粘贴)的菜单相关。
      
      
1
2
3
4
5
6
7
8
9
10
      
      
// These methods provide support for copy/paste actions on cells.
// All three should be implemented if any are.
/** 这些方法为是 复制/粘贴操作相关 */
/** 是否弹出菜单,需要返回YES */
- ( BOOL)collectionView:( UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:( NSIndexPath *)indexPath;
/** 是否可以弹出事件,使copy和paste有效 */
- ( BOOL)collectionView:( UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:( NSIndexPath *)indexPath withSender:( nullable id)sender;
/** 对事件进行相应操作 */
- ( void)collectionView:( UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:( NSIndexPath *)indexPath withSender:( nullable id)sender;
  • 注册类型相关
      
      
1
2
3
4
5
6
7
      
      
/** 注册要使用的cell对应的类型 */
- ( void)registerClass:( nullable Class)cellClass forCellWithReuseIdentifier:( NSString *)identifier;
- ( void)registerNib:( nullable UINib *)nib forCellWithReuseIdentifier:( NSString *)identifier;
/** 注册要使用的补充视图(HeaderView 和 FooterView)对应的类型 */
- ( void)registerClass:( nullable Class)viewClass forSupplementaryViewOfKind:( NSString *)elementKind withReuseIdentifier:( NSString *)identifier;
- ( void)registerNib:( nullable UINib *)nib forSupplementaryViewOfKind:( NSString *)kind withReuseIdentifier:( NSString *)identifier;
  • 复用队列
      
      
1
2
3
      
      
/** 复用队列 */
- (__kindof UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:( NSString *)identifier forIndexPath:( NSIndexPath *)indexPath;
- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:( NSString *)elementKind withReuseIdentifier:( NSString *)identifier forIndexPath:( NSIndexPath *)indexPath;
  • 动态修改当前的Item 和 Section
      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
      
      
// These methods allow dynamic modification of the current set of items in the collection view
/** 这些方法允许动态修改当前的Item 和 Section */
// 插入Section
- ( void)insertSections:( NSIndexSet *)sections;
// 删除Section
- ( void)deleteSections:( NSIndexSet *)sections;
// 刷新Section
- ( void)reloadSections:( NSIndexSet *)sections;
// 移动Section
- ( void)moveSection:( NSInteger)section toSection:( NSInteger)newSection;
// 插入Item
- ( void)insertItemsAtIndexPaths:( NSArray< NSIndexPath *> *)indexPaths;
// 删除Item
- ( void)deleteItemsAtIndexPaths:( NSArray< NSIndexPath *> *)indexPaths;
// 刷新Item
- ( void)reloadItemsAtIndexPaths:( NSArray< NSIndexPath *> *)indexPaths;
// 移动Item
- ( void)moveItemAtIndexPath:( NSIndexPath *)indexPath toIndexPath:( NSIndexPath *)newIndexPath;
/** 同样可以进行批量操作 */
- ( void)performBatchUpdates:( void (^ __ nullable)( void))updates completion:( void (^ __ nullable)( BOOL finished))completion; // allows multiple insert/delete/reload/move calls to be animated simultaneously. Nestable.
  • 其它属性 和 方法相关
      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
      
      
/** 预加载 */
@property ( nonatomic, getter=isPrefetchingEnabled) BOOL prefetchingEnabled NS_AVAILABLE_IOS( 10_0);
/** 允许选择 */
@property ( nonatomic) BOOL allowsSelection; // default is YES
/** 允许多个选择 */
@property ( nonatomic) BOOL allowsMultipleSelection; // default is NO
/** 全局刷新 */
- ( void)reloadData; // discard the dataSource and delegate data and requery as necessary
/** 布局动画 */
- ( void)setCollectionViewLayout:( UICollectionViewLayout *)layout animated:( BOOL)animated; // transition from one layout to another
- ( void)setCollectionViewLayout:( UICollectionViewLayout *)layout animated:( BOOL)animated completion:( void (^ __ nullable)( BOOL finished))completion NS_AVAILABLE_IOS( 7_0);
4、UICollectionViewLayout:自定义布局,它负责了将各个cellSupplementary ViewDecoration Views进行组织。

UICollectionViewLayoutUICollectionView特有的,是UICollectionView的精髓所在,它负责将每个cellsupplementary viewdecoration view进行组合,为它们设置各自的属性,包括:位置、大小、透明度、层级关系、形状等。UICollectionViewLayout决定了,UICollectionView是如何显示在界面上,从UICollectionView初始化必须要一个UICollectionViewLayout也可以看得出来,Layout对于UICollectionView的最要性。

自定义布局:只要了解5个方法(重写它方法,扩展功能)

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
      
      
/**
什么时候调用:collectionView第一次布局,collectionView刷新的时候也会调用
作用:计算cell的布局,条件:ell的位置是固定不变的.
*/
- ( void)prepareLayout;
/**
作用:指定一段区域给你这段区域cell的尺寸(可以一次性返回所有cell尺寸,也可以每隔一个距离返回cell)
系统传递过来一个区域rect,我们需要返回在该区域中的item的位置信息
返回的是一个数组,数组中包含UICollectionViewLayoutAttributes 对象
*/
- ( nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:( CGRect)rect; // return an array layout attributes instances for all the views in the given rect
// 在滚动的时候是否允许刷新(Invalidate)布局
- ( BOOL)shouldInvalidateLayoutForBoundsChange:( CGRect)newBounds; // return YES to cause the collection view to requery the layout for geometry information
/**
什么时候调用:用户手指一松开就会调用
作用:确定最终偏移量
*/
- ( CGPoint)targetContentOffsetForProposedContentOffset:( CGPoint)proposedContentOffset withScrollingVelocity:( CGPoint)velocity; // return a point at which to rest after scrolling - for layouts that want snap-to-point scrolling behavior
- ( CGPoint)targetContentOffsetForProposedContentOffset:( CGPoint)proposedContentOffset NS_AVAILABLE_IOS( 7_0); // a layout can return the content offset to be applied during transition or update animations
/** 由于UICollectionVeiw继承自UIScrollView,所以需要重写该函数,告诉contentSize大小 */
- ( CGSize)collectionViewContentSize;
5、UICollectionViewFlowLayout:主要管理布局信息方面,Apple为我们提供了一个最简单可能也是最常用的默认layout对象,UICollectionViewFlowLayoutFlow Layout简单说是一个直线对齐的layout
  • 我们来了解UICollectionViewFlowLayout它内部常用的属性:
      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
      
      
NS_CLASS_AVAILABLE_IOS( 6_0) @interface UICollectionViewFlowLayout : UICollectionViewLayout
@property ( nonatomic) CGFloat minimumLineSpacing; // 设置行之间的最小间距(竖直)
@property ( nonatomic) CGFloat minimumInteritemSpacing; // 设置2个item之间(列)的最小间隙(水平),
@property ( nonatomic) CGSize itemSize; // 设置item的大小
/** 预设item大小 */
@property ( nonatomic) CGSize estimatedItemSize NS_AVAILABLE_IOS( 8_0); // defaults to CGSizeZero - setting a non-zero size enables cells that self-size via -preferredLayoutAttributesFittingAttributes:
/** 设置滚动方向,默认是竖直滚动 V */
@property ( nonatomic) UICollectionViewScrollDirection scrollDirection; // default is UICollectionViewScrollDirectionVertical
typedef NS_ENUM( NSInteger, UICollectionViewScrollDirection) {
UICollectionViewScrollDirectionVertical, // 默认是竖直滚动
UICollectionViewScrollDirectionHorizontal // 水平滚动
}; // 设置滚动方向,
/** 1.如果是垂直滚动,高度起作用,宽度忽略 / 2.如果是水平滚动,宽度期作用,高度忽略 */
@property ( nonatomic) CGSize headerReferenceSize; // 分组的头部视图的size大小
@property ( nonatomic) CGSize footerReferenceSize; // 分组的尾部视图的size大小
@property ( nonatomic) UIEdgeInsets sectionInset; // 设置区的内边距
// Set these properties to YES to get headers that pin to the top of the screen and footers that pin to the bottom while scrolling (similar to UITableView).
// 头部视图悬停设为YES
@property ( nonatomic) BOOL sectionHeadersPinToVisibleBounds NS_AVAILABLE_IOS( 9_0);
// 尾部视图悬停设为YES
@property ( nonatomic) BOOL sectionFootersPinToVisibleBounds NS_AVAILABLE_IOS( 9_0);
@end
  • 上面对FlowLayout的属性设置,当然代理方法中也有一一对应,UICollectionViewDelegateFlowLayout 常用方法:
      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
      
      
@protocol UICollectionViewDelegateFlowLayout <UICollectionViewDelegate>
@optional
/**
下面的代理方法是针对indexPath对应的item进行个性化设置
如果使用的是UICollectionViewFlowLayout布局,这些代理方法自动调用
*/
/** 设置指定indexPath的单元格的大小(itemSize) */
- ( CGSize)collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:( NSIndexPath *)indexPath;
/** 设置分组中的每一个单元格的上下左右的空白距离(内边距) */
- ( UIEdgeInsets)collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:( NSInteger)section;
/** 设置分组中的单元格的行间距(竖直) */
- ( CGFloat)collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:( NSInteger)section;
/** 设置每行中的item的(列)间距(水平) */
- ( CGFloat)collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:( NSInteger)section;
/** 分组的头部视图的size大小,含义也是有滚动方向决定的 */
- ( CGSize)collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:( NSInteger)section;
/** 分组的尾部视图的size大小,含义也是有滚动方向决定的 */
- ( CGSize)collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:( NSInteger)section;
@end

UIEdgeInsets sectionInset 内边距
sectionInset 内边距

大专栏  iOS UI控件详解—「UICollectionView综合视图」Cell的结构上相对比较简单。">6、UICollectionViewCell:相对于UItableViewCell而言,UIcollectionViewCell没有那么多样式。UIcollectionViewCell不存在所谓的style,也没有titleLabel和内置的imageView属性,UIcollectionViewCell的结构上相对比较简单。
  • cell:本身作为的View,这里应该就是UICollectionReusableView

  • backgroundView :用作cell背景的视图,设置背景图片等。

  • selectedBackgroundViewcell被选中的背景视图

  • contentView :内容视图,自定义的cell时应该将内容放在这个View上

  • 补充UIcollectionView有一个小细节:被选中的cell的自动变化,所有的cell中的子View,也包括contentView中的子View,当cell被选中是,会自动去查找view是否有被选中状态下的改变,如果在contentView中有一个imageViewselectednormal状态下的图片是不同的,那么选中cell这张图片也会从normal变成selected,不需要添加代码。

7、UICollectionViewLayoutAttributes 布局属性:

在了解这个类之前,我们得先疏通一下,UIcollectionView的布局方式,首先我们之前一直提,UIcollectionView的初始化必须有一个UICollectionViewLayout,也就是我们说的,必须要有一个布局格式样式,

那么一个UIcollectionView有那么多的cellsupplementary Viewdecoration ViewUIcollectionViewLayout是如何进行布局显示的呢?

原来从UIcollectionViewLayout开始加载内容的时候,便默默的做了很多事:首先是去调用 prepareLayout 准备布局,然后根据当前屏幕所处位置的合适rect,得到每一个视图的UICollectionViewLayoutAttributes属性,然后在把视图按UICollectionViewLayoutAttributes中的属性描述设置视图具体的centersize等等,期间也会去调用其他方法去确定一些间距。所以UICollectionViewLayoutAttributes是每个视图决定性的布局的属性。

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
      
      
NS_CLASS_AVAILABLE_IOS( 6_0) @interface UICollectionViewLayoutAttributes : NSObject <NSCopying, UIDynamicItem>
@property ( nonatomic) CGRect frame; // 布局视图的frame简单明了
@property ( nonatomic) CGPoint center; // 视图中心点
@property ( nonatomic) CGSize size; // 视图尺寸
@property ( nonatomic) CATransform3D transform3D; // 这个属性可以用来做酷炫的3D动画
@property ( nonatomic) CGRect bounds NS_AVAILABLE_IOS( 7_0);
@property ( nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS( 7_0); // 转场属性
@property ( nonatomic) CGFloat alpha; // 透明度
@property ( nonatomic) NSInteger zIndex; // default is 0 // 层级,数字越大,层级越高(最上面)
@property ( nonatomic, getter=isHidden) BOOL hidden; // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
@property ( nonatomic, strong) NSIndexPath *indexPath; // 如果是cell有对应的indexPath
// 视图标记,是cell还是supplementary View或者decoration View
@property ( nonatomic, readonly) UICollectionElementCategory representedElementCategory;
@property ( nonatomic, readonly, nullable) NSString *representedElementKind; // nil when representedElementCategory is UICollectionElementCategoryCell

UICollectionView与UITableView比较


  • 相同点:

    • 都需要遵守DataSourceDelegate,实现协议方法。

    • 待补充


  • 不同点:

    • UITableView的最大不同,布局交给了指定的UICollectionViewLayout布局对象。
    • UICollectionViewcell 使用必须先注册,使用出列的方式。

    • UICollectionViewSupplementary 补充视图需要先注册(这里可以充当区的头和尾)。

    • 待补充

UICollectionView 使用说明


说明:代码不重要,重要的是思维

  • 创建UICollectionView必须要有布局参数flowLayout;(采用懒加载)
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];

  • cell必须通过注册;
    registerClass: forCellWithReuseIdentifier:

  • 自定义Cell,原因:系统cell没有任何子控件;(添加子控件imagelabel)。
    @interface LNPhotoViewCell : UICollectionViewCell

  • FlowLayout自定义(调整cell尺寸,利用布局就做效果),原因:系统cell中每个item尺寸都一样;(继承flowLayoutLayout)。
    @interface LNFlowLayout : UICollectionViewFlowLayout

  • 自定义布局: 只要了解5个方法(重写它方法,扩展功能)

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
      
      
// 什么时候调用:collectionView第一次布局,collectionView刷新的时候也会调用
// 作用:计算cell的布局,条件:ell的位置是固定不变的.
- ( void)prepareLayout;
// 作用:指定一段区域给你这段区域cell的尺寸(可以一次性返回所有cell尺寸,也可以每隔一个距离返回cell)
- ( nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:( CGRect)rect;
// 在滚动的时候是否允许刷新(Invalidate)布局
- ( BOOL)shouldInvalidateLayoutForBoundsChange:( CGRect)newBounds;
// 什么时候调用:用户手指一松开就会调用
// 作用:确定最终偏移量
- ( CGPoint)targetContentOffsetForProposedContentOffset:( CGPoint)proposedContentOffset withScrollingVelocity:( CGPoint)velocity; // return a point at which to rest after scrolling - for layouts that want snap-to-point scrolling behavior
// 由于UICollectionVeiw继承自UIScrollView,所以需要重写该函数,告诉contentSize大小
- ( CGSize)collectionViewContentSize;

UICollectionView基本使用


初始化
      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
      
      
- ( UICollectionView *)collectionView {
if (_collectionView == nil) {
UICollectionViewFlowLayout *layout = [[ UICollectionViewFlowLayout alloc] init];
//sectionInset 设置区的内边距
layout.sectionInset = UIEdgeInsetsMake( 20, 20, 20, 20);
//设置2个item之间的最小间隙,
layout.minimumInteritemSpacing = 10;
//设置行之间的最小间距
layout.minimumLineSpacing = 10;
//设置滚动方向,默认是垂直滚动
//layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
//如果是垂直滚动,高度起作用,宽度忽略
//如果是水平滚动,宽度期作用,高度忽略
layout.headerReferenceSize = CGSizeMake( 50, 50);
//设置footerView的大小
layout.footerReferenceSize = CGSizeMake( 50, 50);
_collectionView = [[ UICollectionView alloc] initWithFrame: self.view.bounds collectionViewLayout:layout];
_collectionView.backgroundColor = [ UIColor whiteColor];
// 设置代理,遵守协议<UICollectionViewDataSource,UICollectionViewDelegate>
_collectionView.dataSource = self;
_collectionView.delegate = self;
}
return _collectionView;
}
注册UICollectionView使用的cell类型
      
      
1
2
3
4
5
6
7
8
      
      
// 注册要使用的cell对应的类型
[ self.collectionView registerClass:[LNNumberCollectionViewCell class] forCellWithReuseIdentifier:cellID];
// 注册要使用的HeaverView对应的类型
[_collectionView registerClass:[ UICollectionReusableView class] forSupplementaryViewOfKind: UICollectionElementKindSectionHeader withReuseIdentifier: @"HeadViewId"];
// 注册要使用的FooterView对应的类型
[_collectionView registerClass:[ UICollectionReusableView class] forSupplementaryViewOfKind: UICollectionElementKindSectionFooter withReuseIdentifier: @"footViewId"];
实现协议UICollectionViewDataSource
      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
      
      
#pragma mark - UICollectionViewDataSource
// 多少区
- ( NSInteger)numberOfSectionsInCollectionView:( UICollectionView *)collectionView {
return 3;
}
// 多少item
- ( NSInteger)collectionView:( UICollectionView *)collectionView numberOfItemsInSection:( NSInteger)section {
return 20;
}
// item内容
- (__kindof UICollectionViewCell *)collectionView:( UICollectionView *)collectionView cellForItemAtIndexPath:( NSIndexPath *)indexPath {
LNNumberCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];
cell.label.text = [ NSString stringWithFormat: @"%ld",indexPath.item];
cell.photoImageView.image = [ UIImage imageNamed:[ NSString stringWithFormat: @"%ld",indexPath.item + 1]];
//cell.backgroundColor = [UIColor grayColor];
return cell;
}

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
      
      
//和UITableView不同:补充视图需要先注册
//Supplementary 补充视图,这里可以充当区的头和尾
- ( UICollectionReusableView*)collectionView:( UICollectionView *)collectionView viewForSupplementaryElementOfKind:( NSString *)kind atIndexPath:( NSIndexPath *)indexPath{
if([kind isEqual: UICollectionElementKindSectionHeader])
{
//指明是头
UICollectionReusableView *headView = [collectionView dequeueReusableSupplementaryViewOfKind: UICollectionElementKindSectionHeader withReuseIdentifier: @"HeadViewId" forIndexPath:indexPath];
headView.backgroundColor = [ UIColor redColor];
return headView;
}
if ([kind isEqual: UICollectionElementKindSectionFooter]) {
UICollectionReusableView *footView = [collectionView dequeueReusableSupplementaryViewOfKind: UICollectionElementKindSectionFooter withReuseIdentifier: @"footViewId" forIndexPath:indexPath];
footView.backgroundColor = [ UIColor blueColor];
return footView;
}
return nil;
}
实现代理UICollectionViewDelegate
      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
      
      
// 点击item时触发
- ( void)collectionView:( UICollectionView *)collectionView didSelectItemAtIndexPath:( NSIndexPath *)indexPath
{
NSLog( @"(第%ld-区,第%ld-item)",indexPath.section,indexPath.item);
[collectionView cellForItemAtIndexPath:indexPath].backgroundColor = [ UIColor redColor];
}
// 当前item是否可以点击
- ( BOOL) collectionView:( UICollectionView *)collectionView shouldSelectItemAtIndexPath:( nonnull NSIndexPath *)indexPath
{
if (indexPath.row % 2)
{
return YES;
}
return NO;
}

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
      
      
//cell点击时是否高亮,点击cell时的样式和点击后cell的样式
- ( BOOL)collectionView:( UICollectionView *)collectionView shouldHighlightItemAtIndexPath:( NSIndexPath *)indexPath
{
return YES;
}
- ( void)collectionView:( UICollectionView *)collectionView didHighlightItemAtIndexPath:( NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
cell.backgroundColor = [ UIColor redColor];
}
- ( void)collectionView:( UICollectionView *)collectionView didUnhighlightItemAtIndexPath:( NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
cell.backgroundColor = [ UIColor grayColor];
}

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
      
      
// 长按某item,弹出copy和paste的菜单 (这些方法为是 复制/粘贴操作相关)
- ( BOOL)collectionView:( UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:( NSIndexPath *)indexPath
{
return YES;
}
// 使copy和paste有效
- ( BOOL)collectionView:( UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:( NSIndexPath *)indexPath withSender:( nullable id)sender
{
if ([ NSStringFromSelector(action) isEqualToString: @"copy:"] || [ NSStringFromSelector(action) isEqualToString: @"paste:"])
{
return YES;
}
return NO;
}
//
- ( void)collectionView:( UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:( NSIndexPath *)indexPath withSender:( nullable id)sender
{
if([ NSStringFromSelector(action) isEqualToString: @"copy:"])
{
// NSLog(@"-------------执行拷贝-------------");
[_collectionView performBatchUpdates:^{
[_section0Array removeObjectAtIndex:indexPath.row];
[_collectionView deleteItemsAtIndexPaths:@[indexPath]];
} completion: nil];
}
else if([ NSStringFromSelector(action) isEqualToString: @"paste:"])
{
NSLog( @"-------------执行粘贴-------------");
}
}
布局对象UICollectionViewDelegateFlowLayout
      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
      
      
#pragma mark - UICollectionViewDelegateFlowLayout
//下面的代理方法是针对indexPath对应的item进行个性化设置
//如果使用的是UICollectionViewFlowLayout布局,这些代理方法自动调用
- ( CGSize)collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:( NSIndexPath *)indexPath
{
// indexPath.item 指定第几个cell
// NSInteger item = indexPath.item;
// if (item % 3 == 1) {
// return CGSizeMake(100, 150);
// }
// return CGSizeMake(100, 200);
return CGSizeMake( 100, 150);
}
/** 设置分组中的每一个单元格的上下左右的空白距离(内边距) */
- ( UIEdgeInsets)collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:( NSInteger)section;
/** 设置分组中的单元格的行间距(竖直) */
- ( CGFloat)collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:( NSInteger)section;
/** 设置每行中的item的(列)间距(水平) */
- ( CGFloat)collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:( NSInteger)section;
/** 分组的头部视图的size大小,含义也是有滚动方向决定的 */
- ( CGSize)collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:( NSInteger)section;
/** 分组的尾部视图的size大小,含义也是有滚动方向决定的 */
- ( CGSize)collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:( NSInteger)section;

UICollectionView 基本使用

自定义FlowLayout:水平滚动相册


核心代码如下:

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
      
      
// 作用:指定一段区域给你这段区域cell的尺寸(可以一次性返回所有cell尺寸,也可以每隔一个距离返回cell)
// 缩放
- ( nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:( CGRect)rect {
// 设置cell尺寸 => UICollectionViewLayoutAttributes
// 越靠近中心点,距离越小,缩放越大
// 求cell与中心点距离
// 1.获取当前显示cell的布局
NSArray *attrs = [ super layoutAttributesForElementsInRect: self.collectionView.bounds];
for ( UICollectionViewLayoutAttributes *attr in attrs) {
// 2.计算中心点距离
CGFloat delta = fabs((attr.center.x - self.collectionView.contentOffset.x) - self.collectionView.bounds.size.width * 0.5);
// 3.计算比例
CGFloat scale = 1 - delta / ( self.collectionView.bounds.size.width * 0.5) * 0.25;
// 4.缩放
attr.transform = CGAffineTransformMakeScale(scale, scale);
}
return attrs;
}
// 什么时候调用:
// 作用:确定最终偏移量
// 定位:距离中心点越近,这个cell最终展示到中心点
- ( CGPoint)targetContentOffsetForProposedContentOffset:( CGPoint)proposedContentOffset withScrollingVelocity:( CGPoint)velocity {
// 最终偏移量
CGPoint targetP = [ super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity];
CGFloat collectionW = self.collectionView.bounds.size.width;
// 1.获取最终显示的区域
CGRect targetRect = CGRectMake(targetP.x, 0, collectionW, MAXFLOAT);
// 2.获取最终显示的cell
NSArray *attrs = [ super layoutAttributesForElementsInRect:targetRect];
// 3.获取最小间距
CGFloat minDelta = MAXFLOAT;
for ( UICollectionViewLayoutAttributes *attr in attrs) {
// 获取距离中心点的距离,注意:应该用最终的x
CGFloat delta = (attr.center.x - targetP.x) - self.collectionView.bounds.size.width * 0.5;
if (fabs(delta) < fabs(minDelta)) {
minDelta = delta;
}
}
// 移动间距
targetP.x += minDelta;
if (targetP.x < 0) {
targetP.x = 0;
}
// 获取collectionView偏移量
// NSLog(@"%@ %@",NSStringFromCGPoint(targetP),NSStringFromCGPoint(self.collectionView.contentOffset));
return targetP;
}

附图:
UICollectionView 水平滚动相册

期待


  • 如果在阅读过程中遇到 error || new ideas,希望你能 messages 我,我会及时改正谢谢。
  • 点击右上角的 喜欢 和 订阅Rss 按钮,可以收藏本仓库,并在 Demo 更新时收到邮件通知。

猜你喜欢

转载自www.cnblogs.com/liuzhongrong/p/12408025.html