Good articles to my personal technology blog: https://cainluo.github.io/15130820516379.html

We’ve already seen iOS 11’s drag-and-drop feature. We’ve also tried drag-and-drop within a single view and across views, but it’s not quite the same as what we saw in WWDC 2017. Let’s finish up with the last point: drag-and-drop across apps.

If you haven’t read the previous article, check it out:

IOS Development: Dragging and dropping of UICollectionView

Reprint statement: if you need to reprint this article, please contact the author, and indicate the source, and can not modify this article without authorization.

UIDragInteractionDelegate and UIDropInteractionDelegate agent

The key is two UIDragInteractionDelegate and UIDropInteractionDelegate agency agreement.

Respectively in the two protocols define drag-and-drop behavior, their core functionality with UICollectionViewDragDelegate UICollectionViewDropDelegate are similar, but offers more customization options, especially in the animation and safety.

When a drag is started in the source App, a drag session is generated to monitor the drag object, and when it is dragged to the target App, a drop session is generated. The purpose of UIDragSession and UIDropSession is to provide confidence in the drag object provided by the drag agent. Both the actual data and their locations.

To drag can accept, we need to have a UIDragInteraction in source App and configured a UIDragInteractionDelegate, at that time we drag on view objects, entrust will return to one or more UIDragItem object, Each UIDragItem uses the NSItemProvider to share the dragged object.

In the drag and drop, we need to have a contain UIDropInteraction view, whether it would consult the corresponding UIDropInteractionDelegate can handle a drag-and-drop operation, finally agent can UIDragItem from the drag and drop the session object, And use NSItemProvider to load the corresponding data.

Create the source application

Now that we’ve covered the general idea, let’s go straight to the source App.

Create the source application project

Here we create a source program, configure a UIDragInteraction and UIDragInteractionDelegate protocol implementation.

I’m not going to show you the UI interface here, just a UILabel and a UIImageView. Once we’ve configured the UI, let’s do something else:

Configuration UIDragInteraction

Before we start drag and drop, we need to set one of the UIImageView properties, userInteractionEnabled, to YES.

self.imageView.userInteractionEnabled = YES;
Copy the code

Add UIDragInteraction:

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

Proxy methods to implement data sharing:

- (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];
}
Copy the code

Set the page preview when dragging:

- (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];
}
Copy the code

Finally, let’s set whether to restrict the drag and drop session. If set to YES, the system will cancel our drag and drop session, so we’ll set it to NO:

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

This way the source program is basically ok.

Create target App

In the target App, we also have the corresponding content, but there is a button to clear the content. Here we also need to set:

- (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;
}
Copy the code

After the input set, we need to implement the corresponding UIDropInteractionDelegate proxy method:

- (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];
}
Copy the code

In order to better user experience, add a loading progress view LoadingView, code, you can go to the project to find.

Configure the common data model

Now that we’ve written both the source and target applications, we need to focus on the shared data model ImageModel.

In this, we need to comply with NSItemProviderReading, NSItemProviderWriting and NSCoding.

And corresponding to the implementation of their respective methods:

NSCoding protocol method:

#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"];
}
Copy the code

NSItemProviderReading protocol method

+ (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];
}
Copy the code

NSItemProviderWriting protocol method:

- (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];
}
Copy the code

This is fine. In the Demo I did not package this common data model into a Framework, but in a real project I would recommend packaging it into a corresponding Framework.

PS: KUTTypePNG and kUTTypePlainText belong to the MobileCoreServices framework, and are CFString, if you want to use, Remember the guide into the < MobileCoreServices/MobileCoreServices. H > and convert nsstrings type, these are provided by the iOS, there are more types can to UICoreTypes. The view in the h header file.

The final result

conclusion

That’s pretty much the end of drag and drop, but don’t think you’re done, there’s still a lot to learn. Here are a few video addresses for you to learn more:

  • Introduction to drag and drop: apple.co/2vO46Q4
  • Master drag and drop: apple.co/2vOhvYA
  • Drag and drop data transfer: apple.co/2tszCCm

Third party video:

  • Multiple data representations and custom views: bit.ly/2eGhceO
  • UITableView and UICollectionView: bit unobah ly / 2

We’ve written a lot about com.xxx. XXX, which is actually called UTI, and here are two official articles about UTI:

  • UTI
  • UTIs

engineering

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

The last