Drag and Drop - create an NSItemProvider from my Model

5.4k Views Asked by At

Let's go by parts!

I'm trying to implement Drag and Drop in my UICollectionViewController.

The datasource for the UICollectionView is an array of a custom Model Struct I've created.

As required I have set my collectionView.dragDelegate = self and by doing so I've implemented the required protocol function itemsForBeginning session: UIDragSession...

Here's where my problem starts:

struct Model {
    // some variables
    // Some initializations
}

var myModelDatasource: [Model] = [model1, model2, model3, ...] // it's a simple case example

func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
    let item = myModelDatasource[indexPath.row]

    let itemProvider = NSItemProvider(object: item)
    let dragItem = UIDragItem(itemProvider: itemProvider) // <-- ERROR HERE, Even If i force cast as NSItemProviderWriting
    dragItem.localObject = item

    return [dragItem]
}

I cannot create a dragItem because of my model doesn't conform to type NSItemProviderWriting.

If I force a datasource to be of type String and cast the item to NSString it works, but not with my struct Model.

Does anyone know how to resolve this issue?

2

There are 2 best solutions below

4
On BEST ANSWER

You should use a class (not a struct) for your Model, because as you suggested you have to be conform to NSItemProviderWriting (which inherits from NSObjectProtocol):

The protocol you implement on a class to allow an item provider to retrieve data from an instance of the class.

Many APIs expect subclasses of NSObject, hence you have to use a class, Apple blog: struct vs class

So your Model should be something like:

class Model : NSObject, NSItemProviderWriting {
    public static var writableTypeIdentifiersForItemProvider: [String] {
        return [] // something here
    }

    public func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Swift.Void) -> Progress? {
        return nil // something here
    }
}
0
On

Here is complete example which I have implemented

1- model class/struct

2- call setup method in viewDidload

3- implement drag drop collectionview protocols

class ImageRequestModel {
    var uuid = UUID().uuidString
    var filename: String?
    var url: String? // displayable
    var caption:String?
    var image: UIImage? // its mean new or modifiable
  
}

 func setupCollectionView(){
        self.collectionView?.registerCell(id: PhotoCVC.className)
        self.collectionView?.collectionViewLayout = UICollectionViewLayout.createTwoColumnLayout()
        self.collectionView?.dataSource = self
        self.collectionView?.delegate = self
        self.collectionView?.dragInteractionEnabled = true
        self.collectionView?.dragDelegate = self
        self.collectionView?.dropDelegate = self
        self.setupRefreshControl()
    
    }


extension InspectionPhotosView: UICollectionViewDropDelegate {


    func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
        var destinationIndexPath: IndexPath
        if let indexpath = coordinator.destinationIndexPath {
            destinationIndexPath = indexpath
        } else {
            guard let row = self.collectionView?.numberOfItems(inSection: 0) else { return }
            destinationIndexPath = IndexPath(item: row - 1, section: 0)
        }
        if coordinator.proposal.operation == .move {
            Logger.debug(message: "\(destinationIndexPath.row)")
            self.reorderItems(coordinater: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
        }
    }


    func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
//      if session.localDragSession != nil {
//        return UICollectionViewDropProposal(operation: .forbidden)
//      } else {
//        return UICollectionViewDropProposal(
//          operation: .copy,
//          intent: .insertAtDestinationIndexPath)
//      }

        if collectionView.hasActiveDrag {
            return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
        }
        return UICollectionViewDropProposal(operation: .forbidden)
    }

    fileprivate func reorderItems(coordinater: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) {
        if let item = coordinater.items.first, let sourceIndexPath = item.sourceIndexPath {
            collectionView.performBatchUpdates ({


                let object = imageList.remove(at: sourceIndexPath.item)
               // object.order = destinationIndexPath.row
                self.imageList.insert(object, at: destinationIndexPath.item)
                self.updateCollectionView(imageList)
                self.addPhotoView?.isImageSelected = true

                collectionView.deleteItems(at: [sourceIndexPath])
                collectionView.insertItems(at: [destinationIndexPath])


            }, completion: nil)

        }
    }
}



extension InspectionPhotosView: UICollectionViewDragDelegate {
    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        let item = self.imageList[indexPath.row]
        let itemProvider = NSItemProvider(object: item.uuid as NSString)
        let dragItem = UIDragItem(itemProvider: itemProvider)
           return [dragItem]

    }
}