Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drag and Drop asynchronous data fetching

I am trying to implement Drag and Drop into my application which shares images.

All my images are high performance thumbnails (i.e. small size) so I can't use them as my UIDragItem, at least not as the final image.

What I am looking for is a way of providing the URL for my original image and send that off as the UIDragItem and then have the destination fetch the image asynchronously. This is done in the Photos app when an image is stored in iCloud, so it must somehow be possible, I just cant seem to figure out how.

like image 974
Chris Avatar asked Jul 18 '17 09:07

Chris


2 Answers

Turns out the solution is quite simple and is described in session 227 Data Delivery with Drag and Drop during this WWDC.

You basically make whatever object you want to drag conform to NSItemProviderWriting, and then implement two things.

NSItemProviderWriting:

The interface for supporting initialization of an item provider based on an object, used by a source app when providing copied or dragged items.

Step one

Implement writableTypeIdentifiersForItemProvider which will give your receiver an idea of what type of object you are providing. This is an array of type identifiers with decreasing fidelity (they describe this well in the video)

Step two

Implement loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? which does the heavy lifting, this will get called as the receiver tries to load the object you are providing.

Example

You can disregard the specifics of the data fetching below (I am using firebase) but using the native URLSession API would work the same way pretty much.

extension Media: NSItemProviderWriting {
  //Provide the types you want you are supplying
  static var writableTypeIdentifiersForItemProvider: [String]  {
    return [(kUTTypeImage as String)]
  }


  func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
    print("Item provider would like to write item from path: \(metadata.path!)")
    guard let path = metadata.path else { return nil }
    //Allow a maximum of ~30mb to be downloaded into memory if images, 1GB if video.
    let maxSize:Int64 = (isVideo ? 1000 : 30) * 1024 * 1024

    let storage = Storage.storage().reference(withPath: path)
    let progress = Progress(totalUnitCount: 100)
    var shouldContinue = true
    //When the receiver cancels this block is called where we will set the `shouldContinue` to false to cancel the current task
    progress.cancellationHandler = {
      shouldContinue = false
    }
    let task = storage.getData(maxSize: maxSize) { data, error in
      //Once the data is fetched or we encounter an error, call the completion handler
      completionHandler(data, error)
    }

    if !shouldContinue {
      task.cancel()
    }

    task.observe(.progress) { snapshot in
      if let p = snapshot.progress {
        progress.completedUnitCount = Int64(p.fractionCompleted * 100)
      }
    }
    task.observe(.success) { snapshot in
      print(snapshot)
    }
    task.observe(.failure) { snapshot in
      print(snapshot)
    }
    return progress
  }
}

Then in our DragDelegate:

@available(iOS 11, *)
extension GridViewDelegateDataSource: UICollectionViewDragDelegate {
  func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {

    let mediaItem = media[indexPath.item]
    //You can now instantiate an NSItemProvider directly from your object because it conforms to the `NSItemProviderWriting` protocol
    let itemProvider = NSItemProvider(object: mediaItem)
    let dragItem = UIDragItem(itemProvider: itemProvider)
    return [dragItem]
  }
}
like image 148
Chris Avatar answered Oct 09 '22 08:10

Chris


This code for Drag a PHAsset

extension PHAsset : NSItemProviderWriting {
    public static var writableTypeIdentifiersForItemProvider: [String] {
        return UIImage.writableTypeIdentifiersForItemProvider
    }
    public func loadData(withTypeIdentifier typeIdentifier: String,
                         forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
        PHImageManager.default().requestImageData(for: self, options: nil) { (data, _, _, _) in
            completionHandler(data, nil)
        }
        return nil
    }
}

Use:

let item = UIDragItem(itemProvider: NSItemProvider.init(object: yourasset))
like image 36
jianpeng wang Avatar answered Oct 09 '22 07:10

jianpeng wang