引用自:http://www.jianshu.com/p/c59a5c92f859
一、UICollectionVIew基础
第一步:创建布局对象:
如果使用系统的布局类,我们通常是使用其UICollectionViewFlowLayout子类,通过这个子类来创建我们的布局对象。实例:UICollectionVIewFlowLayout *flowLayout = [UICollectionVIewFlowLayout new];
通过这个实例化的对象提供了一些基本的属性,用于帮助我们控制布局的方式,当然这个类也有其自身的协议,通过签署协议可以实现协议中的一些方法,这些方法可以帮助我们实现更加完善的布局。协议名字叫做:“UICollectionVIewDelegateFlowLayout”,这个协议也是实现基本CollectionVIew的三个协议中必须用到的,其他另外两个协议分别是:UICollectionViewDataSource(数据源)、UICollectionViewDelegate。
下面简单介绍一些关于这个类对象用于实现布局的属性有哪些:
@property(nonatomic)CGFloatminimumLineSpacing;// 这个用于指定每个Item的行间距,默认间距是10.
@property(nonatomic)CGFloatminimumInteritemSpacing;//用于指定每个单元的列间距。
@property(nonatomic)CGSizeitemSize;//指定每个cell的size.
@property(nonatomic)CGSizeestimatedItemSizeNS_AVAILABLE_IOS(8_0);//defaults to CGSizeZero - setting a non-zero size enables cells that self-size via -preferredLayoutAttributesFittingAttributes:
@property(nonatomic)UICollectionViewScrollDirectionscrollDirection;// default is UICollectionViewScrollDirectionVertical
@property(nonatomic)CGSizeheaderReferenceSize;
@property(nonatomic)CGSizefooterReferenceSize;//头部增添视图的size
@property(nonatomic)UIEdgeInsetssectionInset;//设置分区的内边距距离上左下右的距离,所以这个参数有四个参数。
// 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).
@property(nonatomic)BOOLsectionHeadersPinToVisibleBoundsNS_AVAILABLE_IOS(9_0);
@property(nonatomic)BOOLsectionFootersPinToVisibleBoundsNS_AVAILABLE_IOS(9_0);c//用于控制是否在当前窗口显示下一个分区的尾增添
第二步:通过布局对象创建UICollectionView,并设置相关属性,重点是给代理属性给定对象。
初始化UICollectionVIew的方法:
Frame:指定UICollectionView的Frame
collectionViewLayout:指定UICollectView的布局对象。
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout*)layoutNS_DESIGNATED_INITIALIZER;
第三步:设置好各个重用标识
这里就不多说了,在代码的全局位置指定三个全局字符常量。
第四步:使用重用标识注册相关的重用池。
cell的重用池的注册方法:
Class:指定用哪个类作为cell,所以我们可以通过新建继承自UICollectionViewLayout的类来自定义cell.
- (void)registerClass:(nullableClass)cellClass forCellWithReuseIdentifier:(NSString*)identifier;
补充视图重用池的注册方法:
forSupplementaryViewOfKind:指定补充视图的类型
withReuseIdentifier:指定一个重用标识符
- (void)registerClass:(nullableClass)viewClass forSupplementaryViewOfKind:(NSString*)elementKind withReuseIdentifier:(NSString*)identifier;
第五步:实现UICollectionViewDataSource的几个代理方法(这里只提几个常用的)
#pragma mark ---- UICollectionViewDataSource
// 指定在一个CollectionView上分区的数量。
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
// 指定在每个分区上Item的数量。
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return _section0Array.count;
}
// 从重用池中取出UICollectionVIew的celle来给每个Item赋值。
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [_collectionView dequeueReusableCellWithReuseIdentifier:cellId forIndexPath:indexPath];
cell.backgroundColor = [UIColor purpleColor];
return cell;
}
// 和UITableView类似,UICollectionView也可设置段头段尾
这个方法中的参数分别是:
collectionView:这个就不解释了,每个协议中必须传入的跟类。
viewForSupplementaryElementOfKind:根据传入的Kind值来判断到底添加的是那种类型的视图。
atIndexPath:这个参数通过传值来确定到底给那个分区补充视图。
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
if([kind isEqualToString:UICollectionElementKindSectionHeader])
{
UICollectionReusableView *headerView = [_collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:headerId forIndexPath:indexPath];
if(headerView == nil)
{
headerView = [[UICollectionReusableView alloc] init];
}
headerView.backgroundColor = [UIColor grayColor];
return headerView;
}
else if([kind isEqualToString:UICollectionElementKindSectionFooter])
{
UICollectionReusableView *footerView = [_collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:footerId forIndexPath:indexPath];
if(footerView == nil)
{
footerView = [[UICollectionReusableView alloc] init];
}
footerView.backgroundColor = [UIColor lightGrayColor];
return footerView;
}
return nil;
}
// 暂时理解为:能够移动的Item,有待验证。
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
// 暂时理解为:给能够移动的Item指定移动路径,有待验证。
moveItemAtIndexPath:能够一定的Item路径。
toIndexPath:移动到的目标路径。
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath
{
}
第六步:实现UICollectionViewDelegateFlowLayout协议方法(这里的很多方法用属性的点语法同样可以实现,建议使用点语法。)
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return (CGSize){cellWidth,cellWidth};
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
return UIEdgeInsetsMake(5, 5, 5, 5);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
return 5.f;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
return 5.f;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
return (CGSize){ScreenWidth,44};
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
{
return (CGSize){ScreenWidth,22};
}
第七步:实现UICollectionViewDelegate方法
// 指定那些Item需要高亮显示
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
// 点击高亮
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
cell.backgroundColor = [UIColor greenColor];
}
// 选中某item
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
}
// 长按某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(@"-------------执行粘贴-------------");
}
}
二、自定义布局
UICollectionView自定义布局
要自定义UICollectionView布局,就要子类化UICollectionViewLayout,然后重写它的一些方法以达到我们自定义布局的需求。下来我们来看看UICollectionViewLayout类里一些比较重要的方法:
- (void)prepareLayout;为layout显示做准备工作,你可以在该方法里设置一些属性。
- (CGSize)collectionViewContentSize;返回layout的size。
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;返回在collectionView的可见范围内(bounds)所有item对应的layoutAttrure对象装成的数组。collectionView的每个item都对应一个专门的UICollectionViewLayoutAttributes类型的对象来表示该item的一些属性,比如bounds,size,transform,alpha等。
- (UICollectionViewLayoutAttributes)layoutAttributesForItemAtIndexPath:(NSIndexPath)indexPath;传入indexPath,返回该indexPath对应的layoutAtture对象。
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;当当前layout的布局发生变动时,是否重写加载该layout。默认返回NO,若返回YES,则重新执行这俩方法:
- (void)prepareLayout;
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;返回layout“最终”的偏移量,何谓“最终”,手指离开屏幕时layout的偏移量不是最终的,因为它有惯性,当它停止时才是“最终”偏移量。
自定义插入删除动画
插入删除的操作
添加在哪触发:
UIBarButtonItem *btnItem = [[UIBarButtonItem alloc] initWithTitle:@"添加"
style:UIBarButtonItemStylePlain
target:self
action:@selector(addItemBtnClick:)];
self.navigationItem.rightBarButtonItem = btnItem;
添加的实现:
// 添加(插入item)
- (void)addItemBtnClick:(UIBarButtonItem *)btnItem
{
[_collectionView performBatchUpdates:^{
// 构造一个indexPath
NSIndexPath *indePath = [NSIndexPath indexPathForItem:_section0Array.count inSection:0];
[_collectionView insertItemsAtIndexPaths:@[indePath]]; // 然后在此indexPath处插入给collectionView插入一个item
[_section0Array addObject:@"x"]; // 保持collectionView的item和数据源一致
} completion:nil];
}
因为是练习Demo,所以暂时把删除的触发源写在了长按某Item弹出菜单的copy按钮里。实际中你可以自定义UICollectionViewCell,添加长按手势,长按抖动出现叉号,然后删除等,随你怎么做。
// copy and paste 的实现
- (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(@"-------------执行粘贴-------------");
}
}
自定义转场动画