专栏名称: CainLuo
iOS
目录
相关文章推荐
938重庆私家车广播  ·  《阿凡达3》预计年底上映,卡梅隆:为三部中最佳电影 ·  14 小时前  
938重庆私家车广播  ·  《阿凡达3》预计年底上映,卡梅隆:为三部中最佳电影 ·  14 小时前  
科幻世界SFW  ·  新书上市 | ... ·  3 天前  
奔腾融媒 都市全接触  ·  今起,呼和浩特开通临时公交专线! ·  3 天前  
51好读  ›  专栏  ›  CainLuo

玩转iOS开发:iOS 11 新特性《高级拖放》

CainLuo  · 掘金  ·  · 2017-12-13 06:34

正文

文章分享至我的个人技术博客: https://cainluo.github.io/15130820516379.html

在这之前, 我们已经知道了 iOS 11 的拖拽功能, 也试过在单个视图里拖拽和跨视图的拖拽, 但好像和我们在看 WWDC 2017 里的不太一样, 这次我们把最后的一点讲完, 就是跨 App 的拖拽.

如果没有了解过之前的文章, 那么可以去看看之前的文章:

玩转iOS开发:iOS 11 新特性《UIKit新特性的基本认识》 玩转iOS开发:iOS 11 新特性《UICollectionView的拖拽》

转载声明:如需要转载该文章, 请联系作者, 并且注明出处, 以及不能擅自修改本文.

UIDragInteractionDelegate和UIDropInteractionDelegate代理

这次重点说的是两个代理协议 UIDragInteractionDelegate UIDropInteractionDelegate .

这两个协议里分别定义了拖放的行为, 它们的核心功能跟 UICollectionViewDragDelegate UICollectionViewDropDelegate 类似, 只不过提供了更多的自定义选项, 特别是在动画和安全性方面.

当在拖动的源 App 开始拖动, 就会生成一个拖动的会话, 用来监督拖动的对象, 拖动到目标的 App 时, 就会生成一个放置的会话, 而 UIDragSession UIDropSession 的目的是为拖放代理所提供的拖动对象的信心, 无论是实际的数据还是它们的位置都有.

为了可以接受拖动, 我们需要在源 App 里有一个 UIDragInteraction 并且配置好一个 UIDragInteractionDelegate , 这时候我们在视图上拖动对象时, 委托就会返回一个或者多个的 UIDragItem 对象, 每个 UIDragItem 都会使用 NSItemProvider 来共享被拖动的对象.

而在拖放时, 我们就需要有一个包含 UIDropInteraction 的视图, 它会咨询对应的 UIDropInteractionDelegate 是否可以处理拖放操作, 最后代理可以从拖放会话中拿到 UIDragItem 对象, 并使用 NSItemProvider 来加载对应的数据.

创建源应用程序

刚刚就把大致的思路讲完了, 现在我们来直接捣鼓一下源 App .

创建源应用程序工程

这里我们创建一个源程序, 配置一个 UIDragInteraction 并且实现 UIDragInteractionDelegate 协议.

UI 界面这里就不展示了, 就一个 UILabel 和一个 UIImageView , 配置好 UI 之后, 我们来捣鼓其他东西:

配置UIDragInteraction

在启动拖放之前, 我们需要把 UIImageView 的某个属性 userInteractionEnabled 设置为 YES .

self.imageView.userInteractionEnabled = YES;

添加 UIDragInteraction :

UIDragInteraction *dragInteraction = [[UIDragInteraction alloc] initWithDelegate:self];
    
[self.view addInteraction:dragInteraction];

实现数据共享的代理方法:

- (NSArray<UIDragItem *> *)dragInteraction:(UIDragInteraction *)interaction
                  itemsForBeginningSession:(id<UIDragSession>)session {
    
    if (!self.imageModel) {
        return @[];
    }
    
    NSItemProvider *itemProvider = [[NSItemProvider alloc] initWithObject:self.imageModel];
    
    UIDragItem *dragItem = [[UIDragItem alloc] initWithItemProvider:itemProvider];
    
    return @[dragItem];
}

设置一下拖动时预览的页面:

- (UITargetedDragPreview *)dragInteraction:(UIDragInteraction *)interaction
                     previewForLiftingItem:(UIDragItem *)item
                                   session:(id<UIDragSession>)session {
    
    UIView *dragView = interaction.view;
    
    if (!dragView && !self.imageModel) {
        
        return [[UITargetedDragPreview alloc] initWithView:interaction.view];
    }
    
    ImageDragView *imageDragView = [[ImageDragView alloc] initWithTitle:self.imageModel.title
                                                                  image:self.imageModel.image];
    
    UIDragPreviewParameters *dragPreviewParameters = [[UIDragPreviewParameters alloc] init];
    
    dragPreviewParameters.visiblePath = [UIBezierPath bezierPathWithRoundedRect:imageDragView.bounds
                                                                   cornerRadius:20];
    
    CGPoint dragPoint = [session locationInView:dragView];
    
    UIDragPreviewTarget *dragPreviewTarget = [[UIDragPreviewTarget alloc] initWithContainer:dragView
                                                                                     center:dragPoint];
    
    return [[UITargetedDragPreview alloc] initWithView:imageDragView
                                            parameters:dragPreviewParameters
                                                target:dragPreviewTarget];
}

- (UITargetedDragPreview *)dragInteraction:(UIDragInteraction *)interaction
                  previewForCancellingItem:(UIDragItem *)item
                               withDefault:(UITargetedDragPreview *)defaultPreview {
    
    UIView *superView = self.imageView.superview;
    
    if (!superView) {
        
        return defaultPreview;
    }
    
    UIDragPreviewTarget *dragPreviewTarget = [[UIDragPreviewTarget alloc] initWithContainer:superView
                                                                                     center:self.imageView.center];
    
    return [[UITargetedDragPreview alloc] initWithView:self.imageView
                                            parameters:[[UIDragPreviewParameters alloc] init]
                                                target:dragPreviewTarget];
}

最后, 我们来设置一下是否要限制这个拖放会话, 如果设置为 YES , 系统就会取消掉我们的拖放会话, 所以这里我们要设置为 NO :

- (BOOL)dragInteraction:(UIDragInteraction *)interaction
sessionIsRestrictedToDraggingApplication:(id<UIDragSession>)session {
    
    return NO;
}

这样子源程序就基本上可以了.

创建目标App

在目标App里, 我们也有对应的内容, 但多了一个清除内容的按钮, 这里我们也要设置一下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.clearButton.springLoaded = YES;
    
    UIDropInteraction *dropInteraction = [[UIDropInteraction alloc] initWithDelegate:self];
    
    [self.view addInteraction:dropInteraction];
    
    [self display];
}

- (IBAction)clearAction:(UIButton *)sender {
    
    self.imageModel = nil;
    self.titleLabel.text = @"";
    
    [self display];
}

- (void)display {
    
    if (!self.imageModel) {
     
        self.imageView.image = nil;
        self.titleLabel.text = @"";
        
        return;
    }
    
    self.imageView.image = self.imageModel.image;
    self.titleLabel.text = self.imageModel.title;
}

做好前期设置之后, 我们就需要去实现对应的 UIDropInteractionDelegate 的代理方法:

- (BOOL)dropInteraction:(UIDropInteraction *)interaction
       canHandleSession:(id<UIDropSession>)session {
    
    return [session canLoadObjectsOfClass:[ImageModel class]];
}

- (UIDropProposal *)dropInteraction:(UIDropInteraction *)interaction
                   sessionDidUpdate:(id<UIDropSession>)session {
    
    return [[UIDropProposal alloc] initWithDropOperation:UIDropOperationCopy];
}

- (void)dropInteraction:(UIDropInteraction *)interaction
            performDrop:(id<UIDropSession>)session {
    
    UIDragItem *dropItem = session.items.lastObject;
    
    if (!dropItem) {
        
        return;
    }
    
    session.progressIndicatorStyle = UIDropSessionProgressIndicatorStyleNone;
    
    self.progress = [dropItem.itemProvider loadObjectOfClass:[ImageModel class]
                                           completionHandler:^(id<NSItemProviderReading>  _Nullable object, NSError * _Nullable error) {
        
                                               self.imageModel = (ImageModel *)object;

                                               if (!self.imageModel) {
                                                   
                                                   return;
                                               }
                                               
                                               dispatch_async(dispatch_get_main_queue(), ^{
                                                   
                                                   [self display];
                                                   
                                                   [self.loadingView removeFromSuperview];
                                                   self.loadingView = nil;
                                               });
                                           }];
}

- (void)dropInteraction:(UIDropInteraction *)interaction
                   item:(UIDragItem *)item
willAnimateDropWithAnimator:(id<UIDragAnimating>)animator {
    
    NSProgress *progress    = self.progress;
    UIView *interactionView = interaction.view;
    
    if (!interactionView || !progress) {
        
        return;
    }
    
    self.loadingView = [[LoadingView alloc] initWithFrame:interactionView.bounds
                                                 progress:progress];
    
    [interactionView addSubview:self.loadingView];
}

这里为了更好的用户体验, 添加了一个加载进度的视图 LoadingView , 代码的话, 可以自行到工程里寻找.

配置公共数据模型

刚刚我们已经把源应用和目标应用都写好了, 这里我们需要重点提一下这个共享的数据模型 ImageModel .

在这里面, 我们要去遵守 NSItemProviderReading , NSItemProviderWriting NSCoding 三个协议.

并且对应的去实现它们各自的方法:

NSCoding 协议方法:

#pragma mark - NSCoding
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    
    UIImage *image  = [UIImage imageWithData:[aDecoder decodeObjectForKey:@"image"]];
    NSString *title = [aDecoder decodeObjectForKey:@"title"];

    return [self initWithTitle:title image:image];
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    
    [aCoder encodeObject:UIImagePNGRepresentation(self.image)
                  forKey:@"image"];
    [aCoder encodeObject:self.title
                  forKey:@"title"];
}

NSItemProviderReading 协议方法:

+ (nullable instancetype)objectWithItemProviderData:(NSData *)data
                                     typeIdentifier:(NSString *)typeIdentifier
                                              error:(NSError **)outError {
    if ([typeIdentifier isEqualToString:IMAGE_TYPE]) {
        
        ImageModel *imageModel = [NSKeyedUnarchiver unarchiveObjectWithData:data];
        
        return [[self alloc] initWithImageModel:imageModel];
    }
    
    return nil;
}

+ (NSArray<NSString *> *)readableTypeIdentifiersForItemProvider {
    
    return @[IMAGE_TYPE];
}

NSItemProviderWriting 协议方法:

- (nullable NSProgress *)loadDataWithTypeIdentifier:(NSString *)typeIdentifier
                   forItemProviderCompletionHandler:(void






请到「今天看啥」查看全文