UICollectionView is more of a control used in the development, this article records UICollectionView in the development of common methods summary, Including the use of UICollectionViewFlowLayout Grid layout, add the Header/Footer, custom layout layout, the other aspects such as adding UICollectionView Cell click effect and so on
In this paper, the Demo: CollectionViewDemo
UICollectionView important concepts
There are several important concepts in UICollectionView, and understanding these important concepts is very helpful in using UICollectionView. The concepts are explained from the dimensions of the user’s data, the data presented by the layout, the View presented by the View, and the role played by the UICollectionView. This part is more conceptual. If you are a pragmatist, You can directly jump to the next part of the “UICollectionView and UICollectionViewFlowLayout” view UICollectionView simple, practical, and then come back to review these concepts, it is also a good way
User data
The DataSource in UICollectionView tells the UICollectionView how many sections it has and how many elements in each section it needs to display. This is similar to the DataSource in UITableView
Layout displayed data
The Layout display data is the Layout in the UICollectionView. The Layout tells the UICollectionView the size and position of the elements in each section. Each element shows the position and size of the information is stored in a UICollectionViewLayoutAttributes class object, Layout object will manage an array contains multiple UICollectionViewLayoutAttributes object. Layout of the corresponding concrete class is UICollectionViewLayout and UICollectionViewFlowLayout, UICollectionViewFlowLayout can be used directly, The simplest way to layout a Grid is by setting the size of each element. If more customization is required, set other attributes such as minimumLineSpacing and minimumInteritemSpacing to set the spacing between elements.
View Displays the View
The DataSource uses the UICollectionViewCell class for each data presentation. In general, subclasses of UICollectionViewCell are created to add required UI elements for custom layout. Can use registerClass: forCellReuseIdentifier: method or registerNib: forCellReuseIdentifier: method of register, Then in the UICollectionView’s DataSource method collectionView: CellForItemAtIndexPath: use the method dequeueReusableCellWithIdentifier: get to the front of the registration of the Cell, using the item need to display the data set.
In addition, if you have special Header/Footer requirements, you need to use the UICollectionReusableView class, which is also used to create a custom UI by creating subclasses. You can use registerClass: forSupplementaryViewOfKind: withReuseIdentifier: method or registerNib: forSupplementaryViewOfKind: withReuseIde Ntifier: method of register, and then in UICollectionView DataSource method collectionView: viewForSupplementaryElementOfKind: AtIndexPath: use the method dequeueReusableSupplementaryViewOfKind: withReuseIdentifier: ForIndexPath: Get the previously registered reusableView and set the data to be displayed.
The role that UICollectionView plays
The role of UICollectionView is a container class, which is an intermediary. It is used to connect the relationship between DataSource, Layout and UI, and plays a coordinating role. The role of CollectionView can be identified by the figure below. 
UICollectionView and UICollectionViewFlowLayout
UICollectionView has prepared for us a Layout of the box class, is UICollectionViewFlowLayout, use UICollectionViewFlowLayout can realize the Grid is often used to form the Layout, Here know the meaning of the commonly used several properties in the UICollectionViewFlowLayout and how to use and customize UICollectionViewFlowLayout.
UICollectionViewFlowLayout header file attributes defined as follows:
@property (nonatomic) CGFloat minimumLineSpacing;
@property (nonatomic) CGFloat minimumInteritemSpacing;
@property (nonatomic) CGSize itemSize;
@property (nonatomic) UICollectionViewScrollDirection scrollDirection;
@property (nonatomic) UIEdgeInsets sectionInset;
Copy the code
-
MinimumLineSpacing if itemSize is the same, the true LineSpacing is minimumLineSpacing. If the heights are not the same, then this value is the height between the highest Y-axis value in the previous row and the lowest Y-axis value in the current row. Other elements in the row have a greater link acing than minimum Link Acing
-
MinimumInteritemSpacing is defined as the spacing between the element levels. This spacing will be greater than or equal to the value we set, because it is possible that a row will not fit more than N elements, and there will be M units of space. The remaining space will be evenly allocated to the spacing of the elements. The actual IteritemSpacing is (minimumInteritemSpacing + M/(n-1))

-
ItemSize itemSize indicates the size of the Cell
-
ScrollDirection is shown in the figure below, indicating the scrolling direction of UICollectionView. UICollectionViewScrollDirectionVertical can set the vertical direction and horizontal direction UICollectionViewScrollDirectionHorizontal 
-
SectionInset defines the inner margin between the upper, lower, left and right of the Cell region relative to the UICollectionView region, as shown in the set below
After understanding the concepts of UICollectionViewFlowLayout, we implement a layout effect to the following form
1. Initialization and UICollectionView UICollectionViewFlowLayout initialization
The first to use UICollectionViewFlowLayout object initialization UICollectionView objects, UICollectionViewFlowLayout object set show that the size of the item element, rolling direction, padding, line spacing, element spacing, Make a row that shows exactly two elements with an inner margin of 5, element spacing of 10, and line spacing of 20, as shown above. Here there is an important operation is to use registerClass: forCellWithReuseIdentifier: registered Cell method, for later use.
- (UICollectionView *)collectionView {
if (_collectionView == nil) {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
CGFloat itemW = (SCREEN_WIDTH - 20) / 2;
CGFloat itemH = itemW * 256 / 180;
layout.itemSize = CGSizeMake(itemW, itemH);
layout.sectionInset = UIEdgeInsetsMake(5.5.5.5);
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.minimumLineSpacing = 20;
layout.minimumInteritemSpacing = 10;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_collectionView.backgroundColor = [UIColor whiteColor];
_collectionView.delegate = self;
_collectionView.dataSource = self;
[_collectionView registerClass:[TTQVideoListCell class] forCellWithReuseIdentifier:@"TTQVideoListCell"];
}
return _collectionView;
}
Copy the code
2. UICollectionViewDataSource processing
- rewrite
collectionView: numberOfItemsInSection:
Return the number of elements - rewrite
collectionView: cellForItemAtIndexPath:
, the use ofdequeueReusableCellWithReuseIdentifier:
Get the reused Cell, set the data of the Cell, and return the Cell - rewrite
collectionView: didSelectItemAtIndexPath:
This step is not necessary, but most scenarios require interaction, and clicking on the Cell requires some processing, so add this method here, and perform a deselect state processing here
// MARK: - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.dataSource.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
TTQVideoListCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TTQVideoListCell" forIndexPath:indexPath];
TTQVideoListItemModel *data = self.dataSource[indexPath.item];
[cell setupData:data];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
TTQVideoListItemModel *data = self.dataSource[indexPath.item];
[collectionView deselectItemAtIndexPath:indexPath animated:YES];
// FIXME:ZYT handles jumps
}
Copy the code
The data source is a simple one-dimensional array, as follows
- (NSMutableArray *)dataSource {
if(! _dataSource) { _dataSource = [NSMutableArray array];
// FIXME: ZYT TEST
for (int i = 0; i < 10; i++) {
TTQVideoListItemModel *data = [TTQVideoListItemModel new];
data.images = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1534329621698&di=60249b63257061ddc1f922bf55dfa0f4& imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fimgad%2Fpic%2Fitem%2Fd009b3de9c82d158e0bd1d998b0a19d8bc3e42de.jpg"; [_dataSource addObject:data]; }}return _dataSource;
}
Copy the code
In this demonstration project, the Cell is implemented in code that inherits the UICollectionViewCell
The header file:
@interface TTQVideoListCell : UICollectionViewCell
- (void)setupData:(TTQVideoListItemModel *)data;
@end
Copy the code
Implementation file:
@interface TTQVideoListCell(a)
@property (nonatomic.strong) UIImageView *coverImageView;
@property (nonatomic.strong) UIView *titleLabelBgView;
@property (nonatomic.strong) UILabel *titleLabel;
@property (nonatomic.strong) UILabel *playCountLabel;
@property (nonatomic.strong) UILabel *praiseCountLabel;
@property (nonatomic.strong) UILabel *statusLabel;
@property (nonatomic.strong) UILabel *tagLabel;
@property (nonatomic.strong) TTQVerticalGradientView *bottomGradientView;
@property (nonatomic.strong) TTQVerticalGradientView *topGradientView;
@property (strong.nonatomic) UIView *highlightView;
@end
@implementation TTQVideoListCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {[self setupUI];
}
return self;
}
- (void)setHighlighted:(BOOL)highlighted {
[super setHighlighted:highlighted];
if (highlighted) {
self.highlightView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
} else {
self.highlightView.backgroundColor = [UIColor colorWithWhite:0 alpha:0]; }} - (void)setupUI {
self.contentView.layer.cornerRadius = 4;
self.contentView.layer.masksToBounds = YES;
[self.contentView addSubview:self.coverImageView];
[self.contentView addSubview:self.topGradientView];
[self.contentView addSubview:self.bottomGradientView];
[self.contentView addSubview:self.titleLabelBgView];
[self.titleLabelBgView addSubview:self.titleLabel];
[self.contentView addSubview:self.playCountLabel];
[self.contentView addSubview:self.praiseCountLabel];
[self.contentView addSubview:self.statusLabel];
[self addSubview:self.tagLabel];
[self addSubview:self.highlightView];
// The layout is omitted. For details, see the code in git repository
}
- (void)setupData:(TTQVideoListItemModel *)data {
self.titleLabel.text = data.title;
self.playCountLabel.text = @" Play times";
self.praiseCountLabel.text = @" Likes";
[self.coverImageView sd_setImageWithURL:[NSURL URLWithString:data.images]];
if (data.status == TTQVideoItemStatusReviewRecommend) {
self.tagLabel.hidden = NO;
self.statusLabel.hidden = YES;
self.tagLabel.text = data.status_desc;
} else {
self.tagLabel.hidden = YES;
self.statusLabel.hidden = NO;
self.statusLabel.text = data.status_desc; }}Copy the code
A CollectionView with a Header/Footer effect can be used to create a Grid layout for the CollectionView. A CollectionView with a Header/Footer effect can be used to create a Grid layout
UICollectionViewFlowLayout Header and Footer
Header and Footer in UICollectionView are also commonly used. The following three steps are similar to those of the Cell, so they are very simple
**1. Register Header/Footer ** Use registerClass: forSupplementaryViewOfKind: withReuseIdentifier: method or registerNib: forSupplementaryViewOfKind: withReuseIdent Ifier: method registration
[_collectionView registerClass:SimpleCollectionHeaderView.class forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"SimpleCollectionHeaderView"];
[_collectionView registerClass:SimpleCollectionFooterView.class forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"SimpleCollectionFooterView"];
Copy the code
**2. Get Header/Footer **
- rewrite
collectionView: layout: referenceSizeForHeaderInSection:
Return the height of the header - rewrite
collectionView: layout: referenceSizeForFooterInSection:
Returns the height of footer - rewrite
collectionView: viewForSupplementaryElementOfKind: atIndexPath:
Method, use methoddequeueReusableSupplementaryViewOfKind: withReuseIdentifier: forIndexPath:
Get the previously registered reusableView and set the data that needs to be displayed. The kind parameter in this method can be usedUICollectionElementKindSectionHeader
,UICollectionElementKindSectionFooter
Two constants to determine whether it’s footer or header
// MARK: Header/Footer - (CGSize)collectionView (UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {return CGSizeMake(SCREEN_WIDTH, 40);
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section {
return CGSizeMake(SCREEN_WIDTH, 24);
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
UICollectionReusableView *supplementaryView = nil;
SectionDataModel *sectionData = self.dataSource[indexPath.section];
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
SimpleCollectionHeaderView* header = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"SimpleCollectionHeaderView" forIndexPath:indexPath];
header.descLabel.text = sectionData.title;
supplementaryView = header;
} else {
SimpleCollectionFooterView* footer = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"SimpleCollectionFooterView" forIndexPath:indexPath];
footer.descLabel.text = [NSString stringWithFormat:@"%@ data", @(sectionData.items.count)];
supplementaryView = footer;
}
return supplementaryView;
}
Copy the code
The Header/Footer class inherits the UICollectionReusableView class, and then implements the custom UI layout. The following implementation is a simple Header, only a Label to display the classification title. Note that you need to subclass UICollectionReusableView to take advantage of the reuse mechanism header in CollectionView
@interface SimpleCollectionHeaderView : UICollectionReusableView
@property (nonatomic.strong) UILabel *descLabel;
@end
Copy the code
Implementation file
@implementation SimpleCollectionHeaderView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {[self addSubview:self.descLabel];
self.backgroundColor = [UIColor colorWithWhite:0.95 alpha:0.6];;
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.descLabel.frame = CGRectMake(15.0.self.bounds.size.width - 30.self.bounds.size.height);
}
- (UILabel *)descLabel {
if(! _descLabel) { _descLabel = [UILabel new];
_descLabel.font = [UIFont systemFontOfSize:18];
_descLabel.textColor = [UIColor colorWithWhite:0.7 alpha:1];
}
return _descLabel;
}
@end
Copy the code
A custom Layout
Custom Layout provides maximum flexibility for CollectionView Layout. It is possible to use custom Layout to realize complex Layout view. For more details, check out the code for ClassHierarchicalTree, an open source project. The set of custom layouts implemented in the Demo project looks like this
To customize a Layout, go through the following steps
- Preprocessing, this step is optional and can be done in this method to improve performance
- Provide the ContentSize
- LayoutAttributes provides LayoutAttributes, which is an array that represents the layout parameters of the Cell displayed on the Item visible in the UICollectionView
- Provides separate Attributes, layout parameters associated with IndexPath
As a simple practice, this article does not do preprocessing, so there are only the next three steps. The following macro definitions will be used in the following code one by one.
/** Cell margins */
#define VideoListCellMargin 5
/** Cell width */
#define VideoListCellWidth ((SCREEN_WIDTH - VideoListCellMargin * 3) / 2)
/** Cell height */
#define VideoListCellHeight (VideoListCellWidth * 265 / 180)
Copy the code
The following code uses headerHeight to represent the height of the header view and datas to represent the data source
@interface TTQVideoListLayout : UICollectionViewLayout
@property (nonatomic.strong) NSArray<TTQVideoListItemModel *> *datas;
/** Height of the header view */
@property (nonatomic.assign) CGFloat headerHeight;
@end
Copy the code
Provide the ContentSize
The ContentSize concept is similar to the ContentSize concept in ScrollView. It represents the size of all the contents. The following code calculates the final display size based on the size of the DataSource array and the value of headerHeight
- (CGSize)collectionViewContentSize {
return CGSizeMake(SCREEN_WIDTH, ceil((CGFloat)self.datas.count / (CGFloat)2) * (VideoListCellHeight + VideoListCellMargin) + self.headerHeight + VideoListCellMargin);
}
Copy the code
Provide LayoutAttributes
The return value is an array that represents the layout parameters of the Cell displayed on items within the range of visibility in the UICollectionView. The Visible Rect in the above figure indicates the layout attributes of all elements in the position
The implementation is very simple, by traversing the layout attributes of the entire content, determine whether there is an intersection with the recT of the display area, if there is an intersection, add the layout attribute object to the array, and finally return the array.
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *array = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < self.datas.count; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
if (!CGRectEqualToRect(attributes.frame, CGRectZero)) {
if (CGRectIntersectsRect(rect, attributes.frame)) { [array addObject:attributes]; }}}return array;
}
Copy the code
Provide separate Attributes
This method is used to return and the layout of the separate IndexPath related attribute object, according to the row in IndexPath parameters can know the position of the element, and then can calculate the corresponding location size, and then initializes a UICollectionViewLayoutAttributes object, Set the parameter value, return UICollectionViewLayoutAttributes object
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
if (indexPath.row < self.datas.count) {
id item = self.datas[indexPath.row];
if ([item isKindOfClass:[TTQVideoListItemModel class]]) {
CGFloat originX = (indexPath.row % 2= =0)? (VideoListCellMargin) : (VideoListCellMargin *2 + VideoListCellWidth);
CGFloat originY = indexPath.row/ 2 * (VideoListCellMargin + VideoListCellHeight) + VideoListCellMargin + self.headerHeight;
attributes.frame = CGRectMake(originX, originY, VideoListCellWidth, VideoListCellHeight);
} else {
attributes.frame = CGRectZero; }}else {
attributes.frame = CGRectZero;
}
return attributes;
}
Copy the code
other
Cell click effect is very often used. Here, we will mainly talk about the realization of two kinds of Cell click effect
Cell Click effect
There are two methods to achieve the effect of CollectionViewCell click. One is to set the properties of CollectionViewCell selectedBackgroundView and backgroundView. Another option is to rewrite the setHighlighted method to set the highlighting state of a custom background View
Set selectedBackgroundView and backgroundView
On the left is the click effect and on the right is the normal state
UIView *selectedBackgroundView = [UIView new];
selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
self.selectedBackgroundView = selectedBackgroundView;
UIView *backgroundView = [UIView new];
backgroundView.backgroundColor = [UIColor clearColor];
self.backgroundView = backgroundView;
Copy the code
This method has a limitation. As shown in the figure below, selectedBackgroundView and backgroundView are located at the bottom of the Cell. If there is a custom layer covering the selectedBackgroundView and backgroundView, such as a Cell with an ImageView filled with Cell views, the clicking effect will not be visible. 
Rewrite the setHighlighted method
This method is similar to customizing the highlighting state of UITableViewCell. SetHighlighted method sets the style of arbitrary UI elements by judging different states. We can add a custom highlighted View at the top of the Cell so that the highlights are not obscured by the cell-filled UI, as shown below
- (void)setupUI {
/ /...
[self addSubview:self.highlightView];
[self.highlightView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
}];
}
- (UIView *)highlightView {
if(! _highlightView) { _highlightView = [UIView new];
_highlightView.backgroundColor = [UIColor clearColor];
_highlightView.layer.cornerRadius = 3;
}
return _highlightView;
}
- (void)setHighlighted:(BOOL)highlighted {
[super setHighlighted:highlighted];
if (highlighted) {
self.highlightView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
} else {
self.highlightView.backgroundColor = [UIColor colorWithWhite:0 alpha:0]; }}Copy the code
The effect is shown below:
reference
Collection View Programming Guide for iOS Custom Collection View layout