iOS开发iOS && Androiddemo

史上第二走心的 iOS11 Drag & Drop 教程

2017-11-02  本文已影响1733人  si1ence

话不多说,先上效果图

普通view拖拽效果 TableView拖拽效果 CollectionView效果 muti-touch效果 多app交互

世界上最大的男性交友网站有demo

一.Tips:你必须要知道的概念

1. Drag 和 Drop 是什么呢?
drag和drop的基本交互图和支持的控件
2. 安全性:
3. dragSession 的过程
4. Others
iPad 可实现的功能还是很丰富的

二、以CollectionView 为例,讲一下整个拖拽的api使用情况

在API设计方面,分为两个步骤:Drag 和 Drop,对应着两套协议 UICollectionViewDragDelegate
UICollectionViewDropDelegate,因此在创建 CollectionView 的时候要增加以下代码:

- (void)buildCollectionView {
    _collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:flowLayout];
    [_collectionView registerClass:[WPFImageCollectionViewCell class] forCellWithReuseIdentifier:imageCellIdentifier];
    _collectionView.delegate = self;
    _collectionView.dataSource = self;
    // 设置代理对象
    _collectionView.dragDelegate = self;
    _collectionView.dropDelegate = self;

    _collectionView.dragInteractionEnabled = YES;
    _collectionView.reorderingCadence = UICollectionViewReorderingCadenceImmediate;
    _collectionView.springLoaded = YES;
    _collectionView.backgroundColor = [UIColor whiteColor];
}
1. 创建CollectionView注意点总结:
- (NSArray<UIDragItem *> *)collectionView:(UICollectionView *)collectionView itemsForAddingToDragSession:(id<UIDragSession>)session atIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point {
    NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithObject:self.dataSource[indexPath.item]];
    UIDragItem *item = [[UIDragItem alloc] initWithItemProvider:itemProvider];
    return @[item];
}
再放一遍这个效果图
2. UICollectionViewDragDelegate(初始化和自定义拖动方法)

由于是返回一个数组,因此可以根据自己的需求来实现该方法:比如拖拽一个item,就可以把该组的所有 item 放进 dragSession 中,右上角会有小蓝圈圈显示个数(但是这种情况下要对数组进行重新排序,因为数组中的最后一个元素会成为Lift 操作中的最上面的一个元素,排序后可以让最先进入dragSession的item放在lift效果的最前面)

- (NSArray<UIDragItem *> *)collectionView:(UICollectionView *)collectionView itemsForBeginningDragSession:(id<UIDragSession>)session atIndexPath:(NSIndexPath *)indexPath {
    
    NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithObject:self.dataSource[indexPath.item]];
    UIDragItem *item = [[UIDragItem alloc] initWithItemProvider:itemProvider];
    self.dragIndexPath = indexPath;
    return @[item];
}
裁剪图中的某一块区域 选取的区域也可以大于这张图,实现添加相框的效果 再高级的功能可以实现目标区域内添加多个rect到dragSession
- (nullable UIDragPreviewParameters *)collectionView:(UICollectionView *)collectionView dragPreviewParametersForItemAtIndexPath:(NSIndexPath *)indexPath {
    // 可以在该方法内使用 贝塞尔曲线 对单元格的一个具体区域进行裁剪
    UIDragPreviewParameters *parameters = [[UIDragPreviewParameters alloc] init];
    
    CGFloat previewLength = self.flowLayout.itemSize.width;
    CGRect rect = CGRectMake(0, 0, previewLength, previewLength);
    parameters.visiblePath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:5];
    parameters.backgroundColor = [UIColor clearColor];
    return parameters;
}
/* 当 lift animation 完成之后开始拖拽之前会调用该方法
 * 该方法肯定会对应着 -collectionView:dragSessionDidEnd: 的调用
 */
- (void)collectionView:(UICollectionView *)collectionView dragSessionWillBegin:(id<UIDragSession>)session {
    NSLog(@"dragSessionWillBegin --> drag 会话将要开始");
}

// 拖拽结束的时候会调用该方法
- (void)collectionView:(UICollectionView *)collectionView dragSessionDidEnd:(id<UIDragSession>)session {
    NSLog(@"dragSessionDidEnd --> drag 会话已经结束");
}

当然也可以在这些方法里面设置自定义的dragPreview,比如 iPad 中原生的通讯图、地图所展现的功能

在 dragSessionWillBegin 方法里面自定义 preview 视图
3. UICollectionViewDropDelegate(迁移数据和自定义释放动画)
Drop手势的流程图
- (void)collectionView:(UICollectionView *)collectionView performDropWithCoordinator:(id<UICollectionViewDropCoordinator>)coordinator {
    
    NSIndexPath *destinationIndexPath = coordinator.destinationIndexPath;
    UIDragItem *dragItem = coordinator.items.firstObject.dragItem;
    UIImage *image = self.dataSource[self.dragIndexPath.row];
    // 如果开始拖拽的 indexPath 和 要释放的目标 indexPath 一致,就不做处理
    if (self.dragIndexPath.section == destinationIndexPath.section && self.dragIndexPath.row == destinationIndexPath.row) {
        return;
    }
    
    // 更新 CollectionView
    [collectionView performBatchUpdates:^{
        // 目标 cell 换位置
        [self.dataSource removeObjectAtIndex:self.dragIndexPath.item];
        [self.dataSource insertObject:image atIndex:destinationIndexPath.item];
        
        [collectionView moveItemAtIndexPath:self.dragIndexPath toIndexPath:destinationIndexPath];
    } completion:^(BOOL finished) {
        
    }];
    
    [coordinator dropItem:dragItem toItemAtIndexPath:destinationIndexPath];
}
- (UICollectionViewDropProposal *)collectionView:(UICollectionView *)collectionView dropSessionDidUpdate:(id<UIDropSession>)session withDestinationIndexPath:(nullable NSIndexPath *)destinationIndexPath {
    UICollectionViewDropProposal *dropProposal;
    // 如果是另外一个app,localDragSession为nil,此时就要执行copy,通过这个属性判断是否是在当前app中释放,当然只有 iPad 才需要这个适配
    if (session.localDragSession) {
        dropProposal = [[UICollectionViewDropProposal alloc] initWithDropOperation:UIDropOperationCopy intent:UICollectionViewDropIntentInsertAtDestinationIndexPath];
    } else {
        dropProposal = [[UICollectionViewDropProposal alloc] initWithDropOperation:UIDropOperationCopy intent:UICollectionViewDropIntentInsertAtDestinationIndexPath];
    }
    return dropProposal;
}
- (BOOL)collectionView:(UICollectionView *)collectionView canHandleDropSession:(id<UIDropSession>)session {
    // 假设在该 drop 只能在当前本 app中可执行,在别的 app 中不可以
    if (session.localDragSession == nil) {
        return NO;
    }
    return YES;
}
- (nullable UIDragPreviewParameters *)collectionView:(UICollectionView *)collectionView dropPreviewParametersForItemAtIndexPath:(NSIndexPath *)indexPath {

    return nil;
}
/* 当drop会话进入到 collectionView 的坐标区域内就会调用,
 * 早于- [collectionView dragSessionWillBegin] 调用
 */
- (void)collectionView:(UICollectionView *)collectionView dropSessionDidEnter:(id<UIDropSession>)session {
    NSLog(@"dropSessionDidEnter --> dropSession进入目标区域");
}

/* 当 dropSession 不在collectionView 目标区域的时候会被调用
 */
- (void)collectionView:(UICollectionView *)collectionView dropSessionDidExit:(id<UIDropSession>)session {
    NSLog(@"dropSessionDidExit --> dropSession 离开目标区域");
}

/* 当dropSession 完成时会被调用,不管结果如何
 * 适合在这个方法里做一些清理的操作
 */
- (void)collectionView:(UICollectionView *)collectionView dropSessionDidEnd:(id<UIDropSession>)session {
    NSLog(@"dropSessionDidEnd --> dropSession 已完成");
}
4. 优化
5. Placeholder

这个在demo里没写,因为只有iPad才支持 app 间传递数据,我想史上第一走心的教程一定会详细讲述这个方法的

由于loadObject是异步的,因此加载数据和显示preview是两条不同的时间线

- (id<UICollectionViewDropPlaceholderContext>)dropItem:(UIDragItem *)dragItem toPlaceholder:(UICollectionViewDropPlaceholder*)placeholder;

  1. 不要使用 reloadData,使用 performBatchUpdates: 来替代(因为 reloadData 会重设一切,删除一切 PlaceHolder)
  2. 可以使用 collectionView.hasUncommittedUpdates 来判断当前 CollectionView 是否还存在 PlaceHolder
6.数据传输(iPhone 开发者了解概念即可)
// 创建一个 NSItemProvider 对象,传递一个适用的对象
UIImage *image = [UIImage imageNamed:@"photo"];
NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithObject:image];
// 该方法中加载数据的方式是异步的,
NSProgress *progress = [itemProvider loadObjectOfClass:[UIImage class] completionHandler:^(id<NSItemProviderReading>  _Nullable object, NSError * _Nullable error) {
#waning 该回调在一个非主队列进行,如果更新UI要回到主线程
        UIImage *image = (UIImage *)object;
        // 使用image
    }];
// 是否完成
BOOL isFinished = progress.isFinished;
// 当前已完成进度
CGFloat progressSoFar = progress.fractionCompleted;
    
[progress cancel];

三. UIView-Tips

UITableView 的api使用基本和 UICollectionView 一致,在此不再赘述,但是以下UIView的特性还要再强调以下

👆的方法控制效果

文章有点长,感谢您的阅读。
每个iOS开发人员都应该了解的一篇非技术文章
demo地址

参考内容:
Introducing Drag and Drop
Mastering Drag and Drop
Drag and Drop with Collection and Table View
Data Delivery with Drag and Drop

上一篇 下一篇

猜你喜欢

热点阅读