玩轉iOS開發:iOS 11 新特性《高級拖放》

文章分享至個人我的技術博客: 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代理

此次重點說的是兩個代理協議UIDragInteractionDelegateUIDropInteractionDelegate.安全

這兩個協議裏分別定義了拖放的行爲, 它們的核心功能跟UICollectionViewDragDelegateUICollectionViewDropDelegate相似, 只不過提供了更多的自定義選項, 特別是在動畫和安全性方面.微信

當在拖動的源App開始拖動, 就會生成一個拖動的會話, 用來監督拖動的對象, 拖動到目標的App時, 就會生成一個放置的會話, 而UIDragSessionUIDropSession的目的是爲拖放代理所提供的拖動對象的信心, 不管是實際的數據仍是它們的位置都有.session

爲了能夠接受拖動, 咱們須要在源App裏有一個UIDragInteraction而且配置好一個UIDragInteractionDelegate, 這時候咱們在視圖上拖動對象時, 委託就會返回一個或者多個的UIDragItem對象, 每一個UIDragItem都會使用NSItemProvider來共享被拖動的對象.app

而在拖放時, 咱們就須要有一個包含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, NSItemProviderWritingNSCoding三個協議.

而且對應的去實現它們各自的方法:

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: kUTTypePNGkUTTypePlainText是屬於MobileCoreServices框架裏的, 而且是CFString類型, 若是要使用, 記得先導入<MobileCoreServices/MobileCoreServices.h>而且轉換成NSString類型, 這些都是iOS系統所提供的, 還有更多的類型能夠到UICoreTypes.h頭文件裏查看.

最終效果

1

2

總結

拖放的內容講到這裏基本上就已經結束了, 但別覺得就完了咯, 還有不少東西須要咱們去學習下面就放幾個視頻地址給你們瞭解更多:

第三方的視頻:

前面咱們寫了不少關於com.xxx.xxx的東西, 其實叫作UTI, 下面有兩篇關於UTI的官方文章:

工程

https://github.com/CainRun/iOS-11-Characteristic/tree/master/5.AdvancedDragAndDrop

最後

碼字很費腦, 看官賞點飯錢可好

微信

支付寶
相關文章
相關標籤/搜索