文章分享至個人我的技術博客: https://cainluo.github.io/15130820516379.htmlhtml
在這以前, 咱們已經知道了iOS 11
的拖拽功能, 也試過在單個視圖裏拖拽和跨視圖的拖拽, 但好像和咱們在看WWDC 2017
裏的不太同樣, 此次咱們把最後的一點講完, 就是跨App
的拖拽.ios
若是沒有了解過以前的文章, 那麼能夠去看看以前的文章:git
玩轉iOS開發:iOS 11 新特性《UIKit新特性的基本認識》 玩轉iOS開發:iOS 11 新特性《UICollectionView的拖拽》github
轉載聲明:如須要轉載該文章, 請聯繫做者, 而且註明出處, 以及不能擅自修改本文.spring
此次重點說的是兩個代理協議UIDragInteractionDelegate
和UIDropInteractionDelegate
.安全
這兩個協議裏分別定義了拖放的行爲, 它們的核心功能跟UICollectionViewDragDelegate
和UICollectionViewDropDelegate
相似, 只不過提供了更多的自定義選項, 特別是在動畫和安全性方面.微信
當在拖動的源App
開始拖動, 就會生成一個拖動的會話, 用來監督拖動的對象, 拖動到目標的App
時, 就會生成一個放置的會話, 而UIDragSession
和UIDropSession
的目的是爲拖放代理所提供的拖動對象的信心, 不管是實際的數據仍是它們的位置都有.session
爲了能夠接受拖動, 咱們須要在源App
裏有一個UIDragInteraction
而且配置好一個UIDragInteractionDelegate
, 這時候咱們在視圖上拖動對象時, 委託就會返回一個或者多個的UIDragItem
對象, 每一個UIDragItem
都會使用NSItemProvider
來共享被拖動的對象.app
而在拖放時, 咱們就須要有一個包含UIDropInteraction
的視圖, 它會諮詢對應的UIDropInteractionDelegate
是否能夠處理拖放操做, 最後代理能夠從拖放會話中拿到UIDragItem
對象, 並使用NSItemProvider
來加載對應的數據.框架
剛剛就把大體的思路講完了, 如今咱們來直接搗鼓一下源App
.
這裏咱們建立一個源程序, 配置一個UIDragInteraction
而且實現UIDragInteractionDelegate
協議.
UI
界面這裏就不展現了, 就一個UILabel
和一個UIImageView
, 配置好UI
以後, 咱們來搗鼓其餘東西:
在啓動拖放以前, 咱們須要把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裏, 咱們也有對應的內容, 但多了一個清除內容的按鈕, 這裏咱們也要設置一下:
- (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 (^)(NSData * _Nullable data, NSError * _Nullable error))completionHandler {
if ([typeIdentifier isEqualToString:(__bridge NSString *)kUTTypePNG]) {
NSData *imageData = UIImagePNGRepresentation(self.image);
if (imageData) {
completionHandler(imageData, nil);
} else {
completionHandler(nil, nil);
}
} else if ([typeIdentifier isEqualToString:(__bridge NSString *)kUTTypePlainText]) {
completionHandler([self.title dataUsingEncoding:NSUTF8StringEncoding], nil);
} else if ([typeIdentifier isEqualToString:IMAGE_TYPE]) {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self];
completionHandler(data, nil);
}
return nil;
}
+ (NSArray<NSString *> *)writableTypeIdentifiersForItemProvider {
return @[IMAGE_TYPE, (__bridge NSString *)kUTTypePNG, (__bridge NSString *)kUTTypePlainText];
}
複製代碼
這樣子就能夠了, 在Demo
裏我並無把這個公共的數據模型打包成Framework
, 但若是是在實際項目中, 建議打包好成對應的Framework
.
PS:
kUTTypePNG
和kUTTypePlainText
是屬於MobileCoreServices
框架裏的, 而且是CFString
類型, 若是要使用, 記得先導入<MobileCoreServices/MobileCoreServices.h>
而且轉換成NSString
類型, 這些都是iOS
系統所提供的, 還有更多的類型能夠到UICoreTypes.h
頭文件裏查看.
拖放的內容講到這裏基本上就已經結束了, 但別覺得就完了咯, 還有不少東西須要咱們去學習下面就放幾個視頻地址給你們瞭解更多:
第三方的視頻:
前面咱們寫了不少關於com.xxx.xxx
的東西, 其實叫作UTI
, 下面有兩篇關於UTI
的官方文章:
https://github.com/CainRun/iOS-11-Characteristic/tree/master/5.AdvancedDragAndDrop