一个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 = [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

 

posted @ 2014-10-30 11:38  CoderWayne  阅读(20540)  评论(3编辑  收藏  举报