从零开始UICollectionView(2)--可移动的Cell及9.0以下的解决方案

前言

本文的所有代码,是基于上一篇UICollectionView(1)--基本实现的代码上编写的,目的是为了代码的连贯性。在文章系列的结束,会给出代码Demo。

上一节,我们基本实现了一个纵向的UICollectionView,但是所有的Cell都是定住无法移动的。当然我们也可以通过数据源的改动然后[self.mainCollectionView reloadData]来更新UI,甚至只更新局部Cell。

但是更体现交互性质的是,用户可以直接在UI上移动Cell,而你要做的是更改对应数据在数据源中的位置。
我们使用的是长按手势开启UICollectionView,通过长按手势在UICollectionView上的移动位置,来移动Cell,同时不断的更换选中的Cell在数据源中的位置。在长按手势结束或者被打断等状况下结束移动。原理说清楚了,下面直接上代码。





1.为UICollectionView添加长按手势。

-(UICollectionView *)mainCollectionView
{
    if (!_mainCollectionView) {
        UICollectionViewFlowLayout * layout = [UICollectionViewFlowLayout new];
        layout.scrollDirection = UICollectionViewScrollDirectionVertical;
        layout.sectionHeadersPinToVisibleBounds = NO;//头部视图悬停设为YES
        layout.sectionFootersPinToVisibleBounds = NO;//尾部视图悬停设为YES
    
        _mainCollectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
        _mainCollectionView.backgroundColor = [UIColor whiteColor];
        _mainCollectionView.delegate = self;
        _mainCollectionView.dataSource = self;
    
        [_mainCollectionView registerClass:[MainCollectionViewCell class] forCellWithReuseIdentifier:[MainCollectionViewCell reuseIdentifier]];
        [_mainCollectionView registerClass:[MainCollectionHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:[MainCollectionHeaderView reuseIdentifier]];
        [_mainCollectionView registerClass:[MainCollectionFooterView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:[MainCollectionFooterView reuseIdentifier]];
    
        //添加长按手势来移动 item
        UILongPressGestureRecognizer * longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(didReceiveLongPress:)];
        [_mainCollectionView addGestureRecognizer:longPress];
        _mainCollectionView.userInteractionEnabled = YES;
    }
    
    return _mainCollectionView;
}

2.在长按手势对应的方法中开启和移动Cell。
#pragma mark - private methods
//长按手势状态控制移动 item
-(void)didReceiveLongPress:(UILongPressGestureRecognizer *)longpress
{
if (self.mainCollectionView == nil) return;

    switch (longpress.state) {
        case UIGestureRecognizerStateBegan:
        {
            //搜寻长按手势在UICollectionView上的位置,若不为nil则开启移动对应位置Cell
            NSIndexPath * indexpath = [self.mainCollectionView indexPathForItemAtPoint:[longpress locationInView:self.mainCollectionView]];
            if (indexpath == nil) {
                break;
            }
            [self.mainCollectionView beginInteractiveMovementForItemAtIndexPath:indexpath];
        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            //长按后移动的情况下,不断通过手势对应的位置更新被选中的Cell的位置
            [self.mainCollectionView updateInteractiveMovementTargetPosition:[longpress locationInView:self.mainCollectionView]];
        }
            break;
        case UIGestureRecognizerStateEnded:
        {
            //长按手势结束,关闭移动
            [self.mainCollectionView endInteractiveMovement];
        }
            break;
        default:
        {
            //其它情况,关闭移动
            [self.mainCollectionView endInteractiveMovement];
        }
            break;
    }
}

3.在UICollectionViewDataSource方法里面有一个- (BOOL)collectionView:canMoveItemAtIndexPath:NS_AVAILABLE_IOS(9_0);的方法,通过返回一个BOOL值来验证是否可以移动Cell。
//是否允许移动 item
- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0)
{
return YES;
}

4.UI完成后也要更新对应数据在数据源中的位置,系统提供一个- (void)collectionView:moveItemAtIndexPath:toIndexPath: NS_AVAILABLE_IOS(9_0);的回调方法。

//移动 item 行为
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS(9_0)
{
    NSMutableArray * moveArray = [NSMutableArray     arrayWithArray:self.dataArray];

    MainModel * model = moveArray[sourceIndexPath.section]    [sourceIndexPath.item];
    NSMutableArray * subArray = [NSMutableArray arrayWithArray:moveArray[sourceIndexPath.section]];
    [subArray removeObject:model];
    moveArray[sourceIndexPath.section] = [NSArray arrayWithArray:subArray];

    NSMutableArray * subToArray = [NSMutableArray arrayWithArray:moveArray[destinationIndexPath.section]];
    [subToArray insertObject:model atIndex:destinationIndexPath.item];
    moveArray[destinationIndexPath.section] = [NSArray arrayWithArray:subToArray];

    self.dataArray = [NSArray arrayWithArray:moveArray];
}

5.效果图。


自由移动Cell

有朋友问这是iOS9.0之后的方法,那iOS9.0之前呢,解决方法如下:
1.原理:我们在长按的开始,获取长按的Cell,将其复制一份,并将原来的Cell隐藏,让复制的View跟着我们的移动而移动,并且在移动的过程中不断将真实的隐藏了的Cell跟我们移动所到位置的Cell做交换,千万记住,数据源中的交换必须发生在UI上的交换之前。
2.代码:

//长按手势状态控制移动 item
-(void)didReceiveLongPress:(UILongPressGestureRecognizer *)longpress
{
    if (self.mainCollectionView == nil) return;

    CGPoint location = [longpress locationInView:self.mainCollectionView];

    NSIndexPath * indexpath = [self.mainCollectionView indexPathForItemAtPoint:location];

    static UICollectionViewCell * cell;
    static UIView * fakeCell;
    static CGPoint beginLocation;
    static NSIndexPath * beginIndex;

    switch (longpress.state) {
        case UIGestureRecognizerStateBegan:
        {
            cell = [self.mainCollectionView cellForItemAtIndexPath:indexpath];
            cell.hidden = YES;
        
            fakeCell = [self customSnapshoFromView:cell.contentView];
            fakeCell.frame = cell.frame;
            [self.mainCollectionView addSubview:fakeCell];
        
            beginLocation = location;
            beginIndex = indexpath;
        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            CGFloat moveX = location.x-beginLocation.x;
            CGFloat moveY = location.y-beginLocation.y;
        
            fakeCell.center = CGPointMake(fakeCell.center.x+moveX, fakeCell.center.y+moveY);
        
            beginLocation = location;
        
        
            NSIndexPath * nowIndexpath = [self.mainCollectionView indexPathForItemAtPoint:location];
            UIView * nowView = [self.mainCollectionView cellForItemAtIndexPath:nowIndexpath];

            if ((nowIndexpath.section==0&&nowIndexpath.row==0)&&(nowView==nil)) {
            
                nowIndexpath = [NSIndexPath indexPathForItem:[self.mainCollectionView numberOfItemsInSection:beginIndex.section]-1 inSection:beginIndex.section];

            }

            [self changeDataSourcesFromSources:beginIndex toDestination:nowIndexpath];
        
            [self.mainCollectionView moveItemAtIndexPath:beginIndex toIndexPath:nowIndexpath];
        
            beginIndex = nowIndexpath;
        }
            break;
        case UIGestureRecognizerStateEnded:
        {
            [fakeCell removeFromSuperview];
            fakeCell = nil;
        
            cell.hidden = NO;
        }
            break;
        default:
        {            
            [fakeCell removeFromSuperview];
            fakeCell = nil;
        
            cell.hidden = NO;

        }
            break;
    }
}

-(void)changeDataSourcesFromSources:(NSIndexPath *)sourceIndexPath toDestination:(NSIndexPath *)destinationIndexPath
{
    NSMutableArray * moveArray = [NSMutableArray arrayWithArray:self.dataArray];

    MainModel * model = moveArray[sourceIndexPath.section][sourceIndexPath.item];
    NSMutableArray * subArray = [NSMutableArray arrayWithArray:moveArray[sourceIndexPath.section]];
    [subArray removeObject:model];
    moveArray[sourceIndexPath.section] = [NSArray arrayWithArray:subArray];

    NSMutableArray * subToArray = [NSMutableArray arrayWithArray:moveArray[destinationIndexPath.section]];
    [subToArray insertObject:model atIndex:destinationIndexPath.item];
    moveArray[destinationIndexPath.section] = [NSArray arrayWithArray:subToArray];

    self.dataArray = [NSArray arrayWithArray:moveArray];
}

#pragma mark 创建cell的快照
- (UIView *)customSnapshoFromView:(UIView *)inputView {
    // 用cell的图层生成UIImage,方便一会显示
    UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, NO, 0);
    [inputView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
// 自定义这个快照的样子(下面的一些参数可以自己随意设置)
    UIView *snapshot = [[UIImageView alloc] initWithImage:image];
//    snapshot.layer.masksToBounds = NO;
//    snapshot.layer.cornerRadius = 0.0;
//    snapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0);
//    snapshot.layer.shadowRadius = 5.0;
//    snapshot.layer.shadowOpacity = 0.4;
    return snapshot;
}

下节预告:纵向瀑布流展示。

瀑布流

注:若你觉得这文章确实帮到你了,或是支持一下原创技术文章,请为我点个赞。大爷若是还能打赏打赏,那就更好不过了。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,982评论 4 60
  • 猝不及防的降温,心却猝不及防的升温, 天气越来越冷,我也越来越想你,这不刚坐上床,窗外下着雨,调好闹钟,拿好明天穿...
    阿白feeler阅读 144评论 3 1
  • (1) 作为宫崎骏的收官之作,《起风了》收获的评价却并不高。影片抛弃了宫崎骏以往的孩子视角,开始讲述残酷的成人世界...
    四月晴明阅读 818评论 0 1
  • 把时间当作朋友 李笑来 点评 点评此书 八年以前,我就开始纠结于时间,过程中经过多种多样的尝试,可惜过程中没有怎么...
    沉默剑士阅读 1,351评论 0 2
  • 【本文由陶瓷秦大师原创,欢迎转载,请保留版权!】 今天湖南的一个客户跟我讲:虽然陶瓷材质的酒缸酒坛比塑料和不锈钢材...
    陶瓷秦大师阅读 701评论 0 0