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.