iOS UICollectionView: The Complete Guide摘要

来源:互联网 发布:软件水平考试成绩查询 编辑:程序博客网 时间:2024/05/16 01:31

本文的内容来自iOS UICollectionView: The Complete Guide, Second Edition。文章对应的源码可以再http://ashfurrow.com/uicollectionview-the-complete-guide/处下载。

通用

设置cell的大小

如果使用了storyboard,可以在storyboard中直接设置
如果使用了UICollectionViewFlowLayout,可以在UICollectionViewFlowLayout中设置

surveyFlowLayout.itemSize = kMaxItemSize;

可以使用UICollectionViewDelegateFlowLayout中的- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath 方法。

prepareForReuse

在自定义UICollectionViewCell时,可以重写-(void)prepareForReuse函数,用来在复用前进行一些clean up的操作。例如,UICollectionViewCell中有一个label,每次都要给label赋值,如下:

@interface AFCollectionViewCell : UICollectionViewCell@property (nonatomic, copy) NSString *text;@end#import "AFCollectionViewCell.h"@implementation AFCollectionViewCell{    UILabel *textLabel;}#pragma mark - Initialization- (id)initWithFrame:(CGRect)frame{    if (!(self = [super initWithFrame:frame])) return nil;    self.backgroundColor = [UIColor whiteColor];    textLabel = [[UILabel alloc] initWithFrame:self.bounds];    textLabel.textAlignment = NSTextAlignmentCenter;    textLabel.font = [UIFont boldSystemFontOfSize:20];    [self.contentView addSubview:textLabel];    return self;}#pragma mark - Overriden UICollectionViewCell methods-(void)prepareForReuse{    [super prepareForReuse];    self.text = @"";}#pragma mark - Overriden properties-(void)setText:(NSString *)text{    _text = [text copy];    textLabel.text = self.text;}@end

selected、highlighted

cell有两个重要的bool属性:selected和highlighted。highlighted完全由用户的交互决定,当用户的手指按下一个cell时,它就自动的变成highlighted。如果cell支持selection,当用户抬起他的手指,cell就变成selected。cell会保持selected,直到你写的一些代码使它unselected或者用户再次点击它。UICollectionViewCell的层级关系如下所示:

这里写图片描述

backgroundView如果被设置,就会永久的显示。当cell被selected,selectedBackgroundView就会被添加到view的层级中,当cell变成unselected,它就会被移除。
下面通过例子说明。通过如下代码来允许多选:

self.collectionView.allowsMultipleSelection = YES;

通过设置selectedBackgroundView来设置selected的背景view。重写setHighlighted来设置highlighted的样式。自定义的UICollectionViewCell如下:

@interface AFCollectionViewCell : UICollectionViewCell@property (nonatomic, strong) UIImage *image;@end@implementation AFCollectionViewCell{    UIImageView *imageView;}- (id)initWithFrame:(CGRect)frame{    if (!(self = [super initWithFrame:frame])) return nil;    self.backgroundColor = [UIColor whiteColor];    imageView = [[UIImageView alloc] initWithFrame:CGRectInset(self.bounds, 10, 10)];    [self.contentView addSubview:imageView];    UIView *selectedBackgroundView = [[UIView alloc] initWithFrame:CGRectZero];    selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:1.0f alpha:0.8f];    self.selectedBackgroundView = selectedBackgroundView;    return self;}#pragma mark - Overriden UICollectionViewCell methods-(void)prepareForReuse{    [super prepareForReuse];    self.backgroundColor = [UIColor whiteColor];    self.image = nil; //also resets imageView’s image}-(void)setHighlighted:(BOOL)highlighted{    [super setHighlighted:highlighted];    if (self.highlighted)    {        imageView.alpha = 0.8f;    }    else    {        imageView.alpha = 1.0f;    }}#pragma mark - Overridden Properties-(void)setImage:(UIImage *)image{    _image = image;    imageView.image = image;}

运行的效果如下:
这里写图片描述

自定义selection的相关方法
collectionView:shouldHighlightItemAtIndexPath:collectionView:shouldSelectItemAtIndexPath:collectionView:shouldDeselectItemAtIndexPath:

Supplementary views

data soruce为collection view提供了需要配置supplementary views所需的信息,但是supplementary views是由 UICollectionViewLayout对象布局的。
UICollectionViewFlowLayout类中有两个supplementary views:header和footer。
自定义一个AFCollectionHeaderView,如下:

@interface AFCollectionHeaderView : UICollectionReusableView@property (nonatomic, copy) NSString *text;@end#import "AFCollectionHeaderView.h"@implementation AFCollectionHeaderView{    UILabel *textLabel;}- (id)initWithFrame:(CGRect)frame{    if (!(self = [super initWithFrame:frame])) return nil;    textLabel = [[UILabel alloc] initWithFrame:CGRectInset(CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame)), 30, 10)];    textLabel.backgroundColor = [UIColor clearColor];    textLabel.textColor = [UIColor whiteColor];    textLabel.font = [UIFont boldSystemFontOfSize:20];    [self addSubview:textLabel];    return self;}-(void)prepareForReuse{    [super prepareForReuse];    [self setText:@""];}-(void)setText:(NSString *)text{    _text = [text copy];    [textLabel setText:text];}@end

在使用时,设置headerReferenceSize

surveyFlowLayout.headerReferenceSize = CGSizeMake(60, 50);

headerReferenceSize是用来告知collection view的布局对象,header是多大。如果你忘记设置,默认就是0,你的header就不会显示。
当水平滚动时,只有你指定的width被使用,header被垂直的拉伸来填满它的空间。当垂直滚动时,只有height被使用,header被水平拉伸来填满它的空间。如下图所示:

这里写图片描述

注册supplementary views

[surveyCollectionView registerClass:[AFCollectionHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:HeaderIdentifier];

使用header,实现UICollectionViewDataSource的- (UICollectionReusableView )collectionView:(UICollectionView )collectionView viewForSupplementaryElementOfKind:(NSString )kind atIndexPath:(NSIndexPath )indexPath方法。

-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{    //Provides a view for the headers in the collection view    AFCollectionHeaderView *headerView = (AFCollectionHeaderView *)[collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:HeaderIdentifier forIndexPath:indexPath];    if (indexPath.section == 0)    {        //If this is the first header, display a prompt to the user        [headerView setText:@"Tap on a photo to start the recommendation engine."];    }    else if (indexPath.section <= currentModelArrayIndex)    {        //Otherwise, display a prompt using the selected photo from the previous section        AFSelectionModel *selectionModel = selectionModelArray[indexPath.section - 1];        AFPhotoModel *selectedPhotoModel = [self photoModelForIndexPath:[NSIndexPath indexPathForItem:selectionModel.selectedPhotoModelIndex inSection:indexPath.section - 1]];        [headerView setText:[NSString stringWithFormat:@"Because you liked %@...", selectedPhotoModel.name]];    }    return headerView;}

更新数据

performBatchUpdates:completion:是一个用来更新collection view的block。修改collection view内容的方法如下:

insertSections:deleteSections:reloadSections:moveSection:toSection:insertItemsAtIndexPaths:deleteItemsAtIndexPaths:reloadItemsAtIndexPaths:moveItemAtIndexPath:toIndexPath:

支持Cut/Copy/Paste

首先让collection view支持menus。

-(BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath{    return YES;}

接下来,collection view会询问其delegate,它能执行不同的action。

-(BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender{    if ([NSStringFromSelector(action) isEqualToString:@"copy:"])    {        return YES;    }    return NO;}

接下来就是执行对应的操作:

-(void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender{    if ([NSStringFromSelector(action) isEqualToString:@"copy:"])    {        UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];        [pasteboard setString:[[self photoModelForIndexPath:indexPath] name]];    }}

UICollectionViewFlowLayout

UICollectionViewLayout是一个抽象类,是用来被继承的。每一个collection view都有一个布局对象与其紧密相连,这个布局对象用来布局内容。Layout并不关心布局的数据。
UICollectionViewFlowLayout其实是一个grid布局。collection view的调用过程如下:

  1. collection view询问data source有个显示内容的信息。包括section的多少,item的多少,以及每个section的supplymentary view。
  2. collection view从其布局对象获取有关显示cell,supplementary view和decoration view的信息。这些信息都存储在一个叫做UICollectionViewLayoutAttributes的类中。
  3. 最后,collection view会转发这些布局信息给cell supplementary view和decoration view

当现在的layout无效时,可以使用invalidateLayout来重新布局。

这里写图片描述

继承UICollectionViewFlowLayout

为什么选择继承?原因如下:

  • 除了使用代理方法外,修改继承布局对象的属性。To modify the attributes of the layout you’re subclassing beyond what is possible with delegate methods
  • 在布局中嵌入decoration view。To incorporate decoration views in your layout
  • 添加新的类型的supplementary view。To add new kinds of supplementary views
  • 扩展UICollectionViewLayoutAttributes,为你布局类添加新的属性。ToextendUICollectionViewLayoutAttributestoaddnewattributesofitems for your layout class to manage
  • 添加手势支持。To add gesture support
  • 自定义插入、更新、删除的动画。To customize the animation of insertion, update, and deletion updates to the collection view

例如如下图所示,由于cell并不是有同样的size,cell就不会垂直对齐。UICollectionViewFlowLayout并没有提供“均匀”分布。因此,我们可以继承UICollectionViewFlowLayout来完成。
这里写图片描述

创建AFCollectionViewFlowLayout继承自UICollectionViewFlowLayout。接着我们把大部分的布局逻辑从view controller中移出来。

#import <UIKit/UIKit.h>#define kMaxItemDimension   200.0f#define kMaxItemSize        CGSizeMake(kMaxItemDimension, kMaxItemDimension)extern NSString * const AFCollectionViewFlowLayoutBackgroundDecoration;@interface AFCollectionViewFlowLayout : UICollectionViewFlowLayout@end

然后实现init方法,设置一些属性

-(id)init{    if (!(self = [super init])) return nil;    self.sectionInset = UIEdgeInsetsMake(30.0f, 80.0f, 30.0f, 20.0f);    self.minimumInteritemSpacing = 20.0f;    self.minimumLineSpacing = 20.0f;    self.itemSize = kMaxItemSize;    self.headerReferenceSize = CGSizeMake(60, 70);    [self registerClass:[AFDecorationView class] forDecorationViewOfKind:AFCollectionViewFlowLayoutBackgroundDecoration];    insertedSectionSet = [NSMutableSet set];    return self;}

接下来需要重写UICollectionViewFlowLayout的两个方法:layoutAttributesForElementsInRect:layoutAttributesForItemAtIndexPath:,这两个方法在collection view布局它的cell,supplementary view和decoration view的时候,会被调用。在此例中,我们会创建一个私有的、第三方德方法,叫做applyLayoutAttributes,我们稍后讨论。

-(void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)attributes{    // Check for representedElementKind being nil, indicating this is a cell and not a header or decoration view    if (attributes.representedElementKind == nil)    {        CGFloat width = [self collectionViewContentSize].width;        CGFloat leftMargin = [self sectionInset].left;        CGFloat rightMargin = [self sectionInset].right;        NSUInteger itemsInSection = [[self collectionView] numberOfItemsInSection:attributes.indexPath.section];        CGFloat firstXPosition = (width - (leftMargin + rightMargin)) / (2 * itemsInSection);        CGFloat xPosition = firstXPosition + (2*firstXPosition*attributes.indexPath.item);        attributes.center = CGPointMake(leftMargin + xPosition, attributes.center.y);        attributes.frame = CGRectIntegral(attributes.frame);    }}#pragma mark - Overridden Methods#pragma mark Cell Layout-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{    NSArray *attributesArray = [super layoutAttributesForElementsInRect:rect];    NSMutableArray *newAttributesArray = [NSMutableArray array];    for (UICollectionViewLayoutAttributes *attributes in attributesArray)    {        [self applyLayoutAttributes:attributes];        if (attributes.representedElementCategory == UICollectionElementCategorySupplementaryView)        {            UICollectionViewLayoutAttributes *newAttributes = [self layoutAttributesForDecorationViewOfKind:AFCollectionViewFlowLayoutBackgroundDecoration atIndexPath:attributes.indexPath];            [newAttributesArray addObject:newAttributes];        }    }    attributesArray = [attributesArray arrayByAddingObjectsFromArray:newAttributesArray];    return attributesArray;}-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{    UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForItemAtIndexPath:indexPath];    [self applyLayoutAttributes:attributes];    return attributes;}

重写的两个方法都调用了其superclass的实现,用来获取所有的UICollectionViewFlowLayout行为。在我们获取默认的属性后,我们可以做出修改。
在applyLayoutAttributes:方法中,我们首先检查representedElementKind属性。对于正常的UICollectionViewCells,会是nil。否则的话,它将会是collection view注册的supplementary view类型。在本例中,将会是UICollectionElementKindSectionHeader

这里写图片描述

UICollectionView的Decoration View

给UICollectionView加上Decoration View。请参考How to Add a Decoration View to a UICollectionView
与cell和supplementary view不同的是,decoration view不是data-driven,它是layout-driven。如何在collection view中添加decoration view呢?

  1. 创建一个decoration view,继承自UICollectionReusableView
  2. 创建UICollectionViewLayout的子类。也可以继承自UICollectionViewFlowLayout。
  3. 在你的layout中布局注册decoration view。
  4. 在layoutAttributesForElementsInRect:方法中,为你的decoration view 返回适当的属性。
  5. 实现layoutAttributesForDecorationViewOfKind:atIndexPath:方法,为指定的decoration view返回attributes。

注册decoration view

[self registerClass:[AFDecorationView class] forDecorationViewOfKind:AFCollectionViewFlowLayoutBackgroundDecoration];

实现-(UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind atIndexPath:(NSIndexPath *)indexPath方法:

-(UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind atIndexPath:(NSIndexPath *)indexPath{    UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:decorationViewKind withIndexPath:indexPath];    if ([decorationViewKind isEqualToString:AFCollectionViewFlowLayoutBackgroundDecoration])    {        UICollectionViewLayoutAttributes *tallestCellAttributes;        NSInteger numberOfCellsInSection = [self.collectionView numberOfItemsInSection:indexPath.section];        for (NSInteger i = 0; i < numberOfCellsInSection; i++)        {            NSIndexPath *cellIndexPath = [NSIndexPath indexPathForItem:i inSection:indexPath.section];            UICollectionViewLayoutAttributes *cellAttribtes = [self layoutAttributesForItemAtIndexPath:cellIndexPath];            if (CGRectGetHeight(cellAttribtes.frame) > CGRectGetHeight(tallestCellAttributes.frame))            {                tallestCellAttributes = cellAttribtes;            }        }        CGFloat decorationViewHeight = CGRectGetHeight(tallestCellAttributes.frame) + self.headerReferenceSize.height;        layoutAttributes.size = CGSizeMake([self collectionViewContentSize].width, decorationViewHeight);        layoutAttributes.center = CGPointMake([self collectionViewContentSize].width / 2.0f, tallestCellAttributes.center.y);        layoutAttributes.frame = CGRectIntegral(layoutAttributes.frame);        // Place the decoration view behind all the cells        layoutAttributes.zIndex = -1;    }    return layoutAttributes;}
添加动画

UICollectionViewLayout本身是支持动画的。主要要使用的方法有:

  • initialLayoutAttributesForAppearingItemAtIndexPath: 在collection view的一个item被加入或者更新时都会调用。可以使用它,为动画开始时,给item提供一些初始的属性。
  • finalLayoutAttributesForDisappearingItemAtIndexPath:
  • prepareForCollectionViewUpdates:
  • finalizeCollectionViewUpdates

例如

-(void)prepareForCollectionViewUpdates:(NSArray *)updateItems{    [super prepareForCollectionViewUpdates:updateItems];    [updateItems enumerateObjectsUsingBlock:^(UICollectionViewUpdateItem *updateItem, NSUInteger idx, BOOL *stop) {        if (updateItem.updateAction == UICollectionUpdateActionInsert)        {            [insertedSectionSet addObject:@(updateItem.indexPathAfterUpdate.section)];        }    }];}-(void)finalizeCollectionViewUpdates{    [super finalizeCollectionViewUpdates];    [insertedSectionSet removeAllObjects];}-(UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingDecorationElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)decorationIndexPath{    //returning nil will cause a crossfade    UICollectionViewLayoutAttributes *layoutAttributes;    if ([elementKind isEqualToString:AFCollectionViewFlowLayoutBackgroundDecoration])    {        if ([insertedSectionSet containsObject:@(decorationIndexPath.section)])        {            layoutAttributes = [self layoutAttributesForDecorationViewOfKind:elementKind atIndexPath:decorationIndexPath];            layoutAttributes.alpha = 0.0f;            layoutAttributes.transform3D = CATransform3DMakeTranslation(-CGRectGetWidth(layoutAttributes.frame), 0, 0);        }    }    return layoutAttributes;}-(UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath{    //returning nil will cause a crossfade    UICollectionViewLayoutAttributes *layoutAttributes;    if ([insertedSectionSet containsObject:@(itemIndexPath.section)])    {        layoutAttributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];        layoutAttributes.transform3D = CATransform3DMakeTranslation([self collectionViewContentSize].width, 0, 0);    }    return layoutAttributes;}
0 0
原创粉丝点击