首页 > 代码库 > 一个UICollectionView自定义layout的实现

一个UICollectionView自定义layout的实现

 
#import <UIKit/UIKit.h>@interface AppDelegate : UIResponder <UIApplicationDelegate>@property (strong, nonatomic) UIWindow *window;@property (strong, nonatomic) NSMutableArray *letterArray;@end
AppDelegate.h
#import "AppDelegate.h"@interface AppDelegate ()@end@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    self.letterArray = [NSMutableArray array];    for(int i=0; i<26; i++)    {        [self.letterArray addObject:[NSString stringWithFormat:@"%C",(unichar)(65+i)]];    }        return YES;}
AppDelegate.m

 

#import <UIKit/UIKit.h>@interface ViewController : UIViewController@end
ViewController.h
#import "ViewController.h"#import "AppDelegate.h"#import "CollectionViewDataSource.h"#import "DraggableCircleLayout.h"#import "LSCollectionViewHelper.h"@interface ViewController (){    UICollectionView *collectionView;    CollectionViewDataSource *cvDataSource;}@end@implementation ViewController- (IBAction)ChangeLayoutClickHandler:(id)sender{    if([collectionView.collectionViewLayout isKindOfClass:[CircleLayout class]])    {        UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];        collectionView.collectionViewLayout = layout;    }    else    {        CircleLayout *layout = [[CircleLayout alloc] init];        collectionView.collectionViewLayout = layout;    }}- (IBAction)BatchUploadClickHandler:(id)sender{    //这里有个细节需要注意,最好是将删除操作放在添加操作前面,因为无论你顺序如何,始终都会先执行删除操作。    //如果代码顺序是先添加后删除,但实际执行顺序是先删除后添加,可能会因为索引不对影响代码逻辑。    [collectionView performBatchUpdates:^{        NSMutableArray *letterArray = [self getLetterArray];        //删除四个元素        NSIndexPath *path1 = [NSIndexPath indexPathForItem:0 inSection:0];        NSIndexPath *path2 = [NSIndexPath indexPathForItem:1 inSection:0];        NSIndexPath *path3 = [NSIndexPath indexPathForItem:2 inSection:0];        NSIndexPath *path4 = [NSIndexPath indexPathForItem:3 inSection:0];                NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,4)];                [indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){            NSLog(@"%lu", (unsigned long)idx);        }];                [letterArray removeObjectsAtIndexes:indexSet];        NSArray *array = [NSArray arrayWithObjects:path1, path2, path3, path4, nil];        [collectionView deleteItemsAtIndexPaths:array];                //添加一个元素        [letterArray addObject:@"1"];                [collectionView insertItemsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForItem:letterArray.count-1 inSection:0]]];    } completion:nil];}- (void)viewDidLoad{    [super viewDidLoad];//    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];//    CircleLayout *layout = [[CircleLayout alloc] init];    DraggableCircleLayout *layout = [[DraggableCircleLayout alloc] init];        collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(10, 50, 300, 400) collectionViewLayout:layout];        collectionView.backgroundColor = [UIColor grayColor];    collectionView.draggable = YES;        [collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"LetterCell"];    [collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:@"FirstSupplementary" withReuseIdentifier:@"ReuseID"];    [collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:@"SecondSupplementary" withReuseIdentifier:@"ReuseID"];            cvDataSource = [CollectionViewDataSource alloc];    collectionView.dataSource = cvDataSource;    collectionView.delegate = cvDataSource;        [self.view addSubview:collectionView];    UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureHandler:)];    [collectionView addGestureRecognizer:tapRecognizer];}- (void)tapGestureHandler:(UITapGestureRecognizer *)sender{    CGPoint point = [sender locationInView:collectionView];    NSIndexPath *tappedCellPath = [collectionView indexPathForItemAtPoint:point];        NSMutableArray *letterArray = [self getLetterArray];    if(tappedCellPath)    {        //删除点击的cell        [letterArray removeObjectAtIndex:tappedCellPath.item];        [collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:tappedCellPath]];    }    else    {        //如果点击空白处,在末尾添加一个随机小写字母        unichar asciiX = (unichar)[self getRandomNumber:97 to:97+26];        [letterArray addObject:[NSString stringWithFormat:@"%C",asciiX]];        NSIndexPath *path = [NSIndexPath indexPathForItem:letterArray.count-1 inSection:0];        [collectionView insertItemsAtIndexPaths:[NSArray arrayWithObject:path]];    }}- (NSMutableArray *)getLetterArray{    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];        return appDelegate.letterArray;}- (int)getRandomNumber:(int)from to:(int)to{    return (int)(from + (arc4random() % (to-from)));}- (void)didReceiveMemoryWarning {    [super didReceiveMemoryWarning];    // Dispose of any resources that can be recreated.}@end
ViewController.m

 

#import <UIKit/UIKit.h>#import "UICollectionView+Draggable.h"@interface CollectionViewDataSource : NSObject<UICollectionViewDataSource_Draggable, UICollectionViewDelegate>@end
CollectionViewDataSource.h
#import <Foundation/Foundation.h>#import "AppDelegate.h"#import "CollectionViewDataSource.h"@implementation CollectionViewDataSource- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{    return 1;}- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{    return [self getLetterArray].count;}- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"LetterCell" forIndexPath:indexPath];        //先移除可重用cell里面的子元素(否则会出现新旧交叠)    [cell.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];    cell.backgroundColor = [UIColor yellowColor];        UILabel *label = [[UILabel alloc] init];    label.text = [[self getLetterArray] objectAtIndex:indexPath.row];    label.font = [UIFont systemFontOfSize:12];    [label sizeToFit];    label.center = CGPointMake(cell.bounds.size.width/2, cell.bounds.size.height/2);    [cell addSubview:label];        return cell;}- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{    UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"ReuseID" forIndexPath:indexPath];        view.backgroundColor = [UIColor greenColor];        UILabel *label = [[UILabel alloc] init];    label.text = kind;    label.font = [UIFont systemFontOfSize:24];    [label sizeToFit];    label.center = CGPointMake(view.bounds.size.width/2, view.bounds.size.height/2);    [view addSubview:label];        return view;}- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{    NSLog(@"你选择了");    //    [self.myArray removeObjectAtIndex:indexPath.row];//    //    [collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];}- (BOOL)collectionView:(LSCollectionViewHelper *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath{    NSLog(@"canMoveItemAtIndexPath");    return YES;}- (void)collectionView:(LSCollectionViewHelper *)collectionView moveItemAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath{    NSLog(@"moveItemAtIndexPath");        NSMutableArray *data =http://www.mamicode.com/ [self getLetterArray];        NSNumber *index = [data objectAtIndex:fromIndexPath.item];    [data removeObjectAtIndex:fromIndexPath.item];    [data insertObject:index atIndex:toIndexPath.item];}- (NSMutableArray *)getLetterArray{    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];        return appDelegate.letterArray;}@end
CollectionViewDataSource.m

 

#import <UIKit/UIKit.h>@interface MyCollectionReusableView : UICollectionReusableView@end
MyCollectionReusableView.h
#import "MyCollectionReusableView.h"@implementation MyCollectionReusableView- (instancetype)initWithFrame:(CGRect)frame{    self = [super initWithFrame:frame];        if (self)    {        self.backgroundColor = [UIColor orangeColor];                UILabel *label = [[UILabel alloc] init];        label.text = @"Decoration View";        label.font = [UIFont systemFontOfSize:18];        [label sizeToFit];        label.center = CGPointMake(frame.size.width/2, frame.size.height/2);        [self addSubview:label];    }        return self;}@end
MyCollectionReusableView.m

 

#import <UIKit/UIKit.h>@interface CircleLayout : UICollectionViewLayout@end
CircleLayout.h
#import "AppDelegate.h"#import "CircleLayout.h"#import "CollectionViewDataSource.h"#import "MyCollectionReusableView.h"@interface CircleLayout(){    CGSize cvSize;    CGPoint cvCenter;    CGFloat radius;    NSInteger cellCount;}@property (strong, nonatomic) NSMutableArray *indexPathsToAnimate;@end@implementation CircleLayout- (void)prepareLayout{    [super prepareLayout];        [self registerClass:[MyCollectionReusableView class] forDecorationViewOfKind:@"MyDecoration"];        cvSize = self.collectionView.frame.size;    cellCount = [self.collectionView numberOfItemsInSection:0];    cvCenter = CGPointMake(cvSize.width / 2.0, cvSize.height / 2.0);    radius = MIN(cvSize.width, cvSize.height) / 2.5;}- (CGSize)collectionViewContentSize{    return self.collectionView.bounds.size;}- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{    NSMutableArray *array = [NSMutableArray array];        //add cells    for (int i=0; i<cellCount; i++)    {        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];                UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];                [array addObject:attributes];    }        //add first supplementaryView    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];    UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:@"FirstSupplementary" atIndexPath:indexPath];    [array addObject:attributes];        //add second supplementaryView    attributes = [self layoutAttributesForSupplementaryViewOfKind:@"SecondSupplementary" atIndexPath:indexPath];    [array addObject:attributes];        //add decorationView    attributes = [self layoutAttributesForDecorationViewOfKind:@"MyDecoration" atIndexPath:indexPath];    [array addObject:attributes];        return array;}- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];    attributes.size = CGSizeMake(20, 20);    attributes.center = CGPointMake(cvCenter.x + radius * cosf(2 * indexPath.item * M_PI / cellCount),                                    cvCenter.y + radius * sinf(2 * indexPath.item * M_PI / cellCount));        return attributes;}- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath{    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind withIndexPath:indexPath];        attributes.size = CGSizeMake(260, 40);    if([elementKind isEqual:@"FirstSupplementary"])    {        attributes.center = CGPointMake(cvSize.width/2, 40);    }    else    {        attributes.center = CGPointMake(cvSize.width/2, cvSize.height-40);    }        return attributes;}- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath{    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:elementKind withIndexPath:indexPath];    attributes.size = CGSizeMake(140, 40);    attributes.center = CGPointMake(cvSize.width/2, cvSize.height/2);        return attributes;}//当边界更改时是否更新布局- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{    CGRect oldBounds = self.collectionView.bounds;        if (CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds))    {        return YES;    }        return NO;}//通知布局,collection view里有元素即将改变,这里可以收集改变的元素indexPath和action类型。-(void)prepareForCollectionViewUpdates:(NSArray *)updateItems{    [super prepareForCollectionViewUpdates:updateItems];        NSMutableArray *indexPaths = [NSMutableArray array];        for(UICollectionViewUpdateItem *updateItem in updateItems)    {        //UICollectionUpdateActionInsert,        //UICollectionUpdateActionDelete,        //UICollectionUpdateActionReload,        //UICollectionUpdateActionMove,        //UICollectionUpdateActionNone                NSLog(@"before index:%d,after index:%d,action:%d", updateItem.indexPathBeforeUpdate.row,updateItem.indexPathAfterUpdate.row,updateItem.updateAction);                switch (updateItem.updateAction) {            case UICollectionUpdateActionInsert:                [indexPaths addObject:updateItem.indexPathAfterUpdate];                break;            case UICollectionUpdateActionDelete:                [indexPaths addObject:updateItem.indexPathBeforeUpdate];                break;            case UICollectionUpdateActionMove:                [indexPaths addObject:updateItem.indexPathBeforeUpdate];                [indexPaths addObject:updateItem.indexPathAfterUpdate];                break;            default:                NSLog(@"unhandled case: %@", updateItem);                break;        }    }        self.indexPathsToAnimate = indexPaths;}//当一个元素被插入collection view时,返回它的初始布局,这里可以加入一些动画效果。- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath{    UICollectionViewLayoutAttributes *attr = [self layoutAttributesForItemAtIndexPath:itemIndexPath];        if([self.indexPathsToAnimate containsObject:itemIndexPath])    {        attr.transform = CGAffineTransformRotate(CGAffineTransformMakeScale(10,10),M_PI);        attr.center = CGPointMake(CGRectGetMidX(self.collectionView.bounds), CGRectGetMidY(self.collectionView.bounds));        [self.indexPathsToAnimate removeObject:itemIndexPath];    }        return attr;}- (NSArray *)getLetterArray{    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];        return appDelegate.letterArray;}@end
CircleLayout.m

 

#import "CircleLayout.h"#import "UICollectionViewLayout_Warpable.h"@interface DraggableCircleLayout : CircleLayout <UICollectionViewLayout_Warpable>@property (readonly, nonatomic) LSCollectionViewLayoutHelper *layoutHelper;@end
DraggableCircleLayout.h
#import "DraggableCircleLayout.h"#import "LSCollectionViewLayoutHelper.h"@interface DraggableCircleLayout(){    LSCollectionViewLayoutHelper *_layoutHelper;}@end@implementation DraggableCircleLayout- (LSCollectionViewLayoutHelper *)layoutHelper{    if(_layoutHelper == nil) {        _layoutHelper = [[LSCollectionViewLayoutHelper alloc] initWithCollectionViewLayout:self];    }    return _layoutHelper;}- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{    return [self.layoutHelper modifiedLayoutAttributesForElements:[super layoutAttributesForElementsInRect:rect]];}@end
DraggableCircleLayout.m

 

一个UICollectionView自定义layout的实现