正文
文章分享至我的个人技术博客: 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