简体   繁体   中英

-[UIDragSession loadObjectsOfClass:completion:] completionBlock not being called

I'm learning iOS11 new APIs Drag and Drop, but I have some questions. I have two collectionViews which share same type ( DataEntity ) of dataSource array. One is for drag and the other is for drop. That means I want to drag item which contains data ( DataEntity ) from one collectionView to another.

Then I have the question in [-(id<UICollectionDropDelegate>)collectionView:performDropWithCoordinator:] . I can't fetch data ( DataEntity ) passed in my first collectionView because -[UIDragSession loadObjectsOfClass:completion:] completionBlock is not being called. However, if I set other class (such as UIImage.class or NSString.class) to parameter loadObjectsOfClass: completion block will get called, however it is not the compatible class so there's no return objects.

Source Code

Drag collectionView

- (NSArray<UIDragItem *> *)collectionView:(UICollectionView *)collectionView itemsForBeginningDragSession:(id<UIDragSession>)session atIndexPath:(NSIndexPath *)indexPath {

    DataEntity* entity = self.entities[indexPath.row];
    UIDragItem* item = [[UIDragItem alloc] initWithItemProvider:[[NSItemProvider alloc] initWithObject:entity]];
    return @[item];
}

Drop collectionView

- (void)collectionView:(UICollectionView *)collectionView performDropWithCoordinator:(id<UICollectionViewDropCoordinator>)coordinator {
    NSIndexPath* destinationIndexPath = coordinator.destinationIndexPath;
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.collectionView performBatchUpdates:^{
            [coordinator.session loadObjectsOfClass:DataEntity.class completion:^(NSArray<__kindof id<NSItemProviderReading>> * _Nonnull objects) {
                [objects enumerateObjectsUsingBlock:^(__kindof id<NSItemProviderReading>  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                    [self.mutableEntities insertObject:(DataEntity*)obj atIndex:destinationIndexPath.row];
                    [self.collectionView insertItemsAtIndexPaths:@[destinationIndexPath]];
                }];
            }];
        } completion:nil];
    });
}
- (BOOL)collectionView:(UICollectionView *)collectionView canHandleDropSession:(id<UIDropSession>)session {
    BOOL test = [session canLoadObjectsOfClass:DataEntity.class]; // It's YES
    return test;
}

Data entity

- (NSProgress *)loadDataWithTypeIdentifier:(NSString *)typeIdentifier forItemProviderCompletionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler {
    NSData* data = [NSKeyedArchiver archivedDataWithRootObject:self];
    completionHandler(data, nil);
    return nil;
}
+ (NSArray<NSString *> *)writableTypeIdentifiersForItemProvider {
    NSString* identifier = NSStringFromClass(self.class);
    return @[identifier];
}
+ (NSArray<NSString *> *)readableTypeIdentifiersForItemProvider {
    NSString* identifier = NSStringFromClass(self.class);
    return @[identifier];
}
+ (nullable instancetype)objectWithItemProviderData:(nonnull NSData *)data typeIdentifier:(nonnull NSString *)typeIdentifier error:(NSError * _Nullable __autoreleasing * _Nullable)outError {
    DataEntity* entity = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    return entity;
}

EDIT

Ok, I found something. First, if I remove the dispatch_async(dispatch_get_main_queue()) , I will get an error unrecoginized selector -[DataEntity encodeWithCoder:] . And that is the second thing, I forget to make DataEntity conform to NSCoding protocol.

Now the new question is, why I can't call -[UIDragSession loadObjectsOfClass:completion:] in dispatch_main_queue closure, or its completion block won't be called?

It all has to do with the basic principles of asynchronous code execution. In your implementation of collectionView:performDropWithCoordinator: , this code is wrong:

dispatch_async(dispatch_get_main_queue(), ^{
    [coordinator.session loadObjectsOfClass: // ...

When you call dispatch_async , you allow the call to collectionView:performDropWithCoordinator: to return — it comes to an end! But you are doing that before you proceed to load the data. Therefore the drop ends immediately and the session disappears, before you have a chance to pick up the data. When we get to loadObjectsOfClass , there is no data any more; the session has already ended.

In fact, I bet the session at that point is nil . And code sent to a nil object does nothing; that's why your objectWithItemProviderData and the completion handler are never called.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM