Drag and Drop between UICollectionViews

(Code on GitHub – Built for Swift 3.x)

This took me a while to code! Extending the functionality of the drag and rearrange tutorial, we can add the ability to share data between collection views.

We create 3 collection views that feed from a common root view controller which will act as both dataSource and delegate.

Screen Shot 2015-05-05 at 17.08.34

We want to make them exchange data when a cell is dragged across. The data can be anything, from a string to a managed object and as an example we will create a DataItem class that will hold a colour and an id:

Noticed the  Equatable interface? We will need the ‘==’ operator to compare DataItem instances. The implementation is defined outside the class through Swift’s operator overloading:

In the root view controller we create some mock data and hold it inside a single multidimensional array .

Here I used a convention which would normally be considered an anti-pattern but for the purpose of this example it will keep our data tidy: I assigned different tags on each collection view on the Storyboard and used these as offsets to the data array as below:

After this initial setup, we need to start a gesture recogniser. Instead of  placing it in the UICollectionViewLayout object as we did in a previous tutorial we need to abstract it into its own Manager class. To clarify things, here is a rough UML diagram:

I took the same approach as  before by creating a Bundle structure to encapsulate information about the state of the drag, as well as a canvas object where the dragging will be animated.

As we drag a cell, we need to check its relative position on the collection views and call various methods, like moveItemAtIndexPath() that will perform a swap of cells. Although most of this code could be placed in the manager class, it would prohibit the extension of the d&d to non-UICollectionViews. So we will pass this responsibility to the views themselves through the use of protocols.

When the manager sends a didMoveItem for example, a UICollectionView could decide to call the moveItemAtIndexPath while a simple UIView could rearrange its elements manually or do nothing at all.

The first thing that the manager needs to catch is the long press and decide if it is a valid point for a the start drag and drop action. It loops over the views that implement the draggable protocol using the where clause, translating the press point to their coordinates and asking each one if it can start a dragging action.

Internally, each view can decide if it can do that. In our example it is a simple case of whether we get an indexPath back:

Most views will return false as the point will be outside their frame. The one that returns true is held within the bundle together with other information such as the offset to the element dragged:

Next on the flow of the gesture recogniser is the callback on every move of the finger or pen. It has 3 states of interest which will be caught in a case statement and described separately. On .Began we just add the snapshot and notify the view that a dragging action has started.

The collection view that will receive the message will simply store the indexPath of the cell being dragged…

…and check for it inside our root view controller’s cellForItemAtIndexPath.

As well as rendering the hidden state of a cell we also need to be able to move, insert and delete the data. The rationale is that while the collection view is responsible for all UI related tasks for the dragging and dropping, such as inserting and deleting cells in the right positions, the dataSource must be able to predictably update its mode before any visual change is made. Doing it in reverse or forgetting to update the model causes a very predictable and annoying crash! This we will achieve through a special protocol that dataSource targets of a KDDragAndDropCollectionView must implement on top of being UICollectionViewDataSource compliant.

The methods above update the model by leaving it to the controller to decide on how it is done. We can be using CoreData or plain arrays like in this example.

Let’s put everything together then.

On .Changed state of the gesture recogniser we first update the snapshot using the offset we have pre-calculated

Next we loop over our KDDroppable and calculate the intersection between their frames and the snapshot’s frame by projecting them both to the canvas for reference. We calculate the area of the intersection and keep the biggest so as to get the view that our snapshot is mostly on.

We then check to see if it is the first time we are entering into this area and if so we call the willMoveItem method from the KDDroppable protocol. We always call the didMoveItem regardless and update the overDroppableView variable so as not to call the willMoveItem again while we are on top of it:

How are these protocol methods implemented in the collection views?

We check as to whether the data item exists and If it is new we insert it.

On the delegate side we must implement the insertion so that the CollectionViews have the correct data

When the user releases his press, we remove the snapshot from the canvas.

In the stopDragging of the collection view we just nullify the draggingPathOfCellBeingDragged so that when the data gets reloaded there is not cell hidden. There is a fringe case we need to cater for which happens when we are in the middle of a move of cells. In this case the reload will skip these cells and so the hidden cell will never get unhidden. So we need to do that manually.

 

For more tutorials on what’s hottest in iOS

[catlist name=”iOS”]

37 thoughts on “Drag and Drop between UICollectionViews

  1. Hello very nice article but i played with it quite a bit , sometimes randomly if you drag at the end the dragged cell disappears. Can you help with that pls ?

  2. i think the problem is with the func indexPathForCellOverlappingRect( rect : CGRect) -> NSIndexPath? function. When the destination collection view is empty this would always return nil

    1. If you want to make a change on the repository, but all means… I am a little tied down with work at the moment but I would appreciate any help, especially on this subject.

  3. hey neat stuff here.

    just curious, any reason why the individual KDDragAndDropCollectionView doesn’t implement the KDDragAndDropCollectionViewDataSource? I notice the parent viewController implements it instead.

    regards.
    K

    1. It’s standard practice to put the DataSource in a controller rather than a view. That’s what you would probably do if you where coding a simple UICollectionView I guess… The idea is that the view should be agnostic of anything else than drawing itself. I am not as fussy about these things as other developers seem to be but where it is not causing any problems I find it best to follow conventions. Makes it easier for the rest of the team to follow as well… Having said all that, I must confess that my initial implementation was exactly what you suggested 😉

  4. Hi there,
    I’ve been following your code to use it with my custom UICollectionViewCell. It works fine for me so far.Do you have any ideas on how to make one collection only draggable and the other collectionView only droppable?

      1. Hi again,
        It worked after doing it like you suggested. I have an another question if you have time. If you drag a cell and move over to another collectionview ( no dropping) and move it back to the original collection view, It is added to second collectionview and still exists in the first collectionview. I checked this and it is not a cell.hidden=true/false issue. Do you have any ideas?

      2. Hi Michael,

        I am new swift and use came across your tutorial – which is great!!

        I try to archive the same as Joseph but I miss the point on what I have to do with KDDraggable, KDDroppable as you suggested. Do I understand it correctly that I have to split KDDragAndDropCollectionView into two new classes – one for draggable and one for droppable and assign it to the collection view separately?

        if not could you maybe give me some advise on what I have to make one collection view aim as draggable and the other as droppable.

        Thank you for your help!!

        1. KDDraggable and KDDroppable are interfaces. This means you can plug them on any subclass of UIView and implement the methods. (I originally planned to have drag and drop between other things than UICollectionView but never got around to do it). For the moment, if you have 2 collection views, A and B, and both implementing the draggable while only A implements the droppable, then you will protect B from having things dropped on it. Hope that makes sense.

  5. hi sir,
    can i get this code in obj C, coz i want it in my project and my hands are dirty in swift, please help me if you can

  6. Love the article. Do you have any tutorials illustrating how to drag an item from a collection view cell (an image view in my case) to a view controller? Much like the apps where the user can drag a “sticker” onto your picture.

    1. In my current implementation, the Draggable is responsible for returning a snapshot. You could make this snapshot be your UIImageView and not the entire cell. Dragging to a controller is something I do not understand fully.

  7. need help.. is there any solution for replacing the cell.? I’m creating jigsaw puzzle app.
    here it is inserting the cell, but i want to replace the cell with the cell currently being dragged ???
    thank you in advance 🙂

    1. This is not the use case of this component unfortunately. It would require too many changes and could compromise its main use. You can always do a fork on GitHub and try it yourself. If you choose to do this, have another look at the tutorial and it should provide you with enough information to enable you to make the changes needed.

  8. Great tutorial really liked but is there any way to do with headers for collection view in swift.
    i need to create collection view having multiple team along with header name team1 team 2 blah blah and its having member .now i can remove member from team 1 and can insert that member to team 2. how can we do this please give an idea .

    1. Well… it is not implemented. I was hoping of turning this into a proper open source project where people would contribute code and improve what I’ve done. So… nothing is stopping you from helping 😉

  9. hi michael,
    thankyou very much for your tutorial. its enlight me. by theaway is there any stable pods for this kind of dragable and sortable to reorder the data list(per row)?

    1. I do not understand the question. I have not built the project as a POD since it is comprised of very few files that you can just include. Apart from that… You can sort per row or any other way…

  10. Hello. How to replace the items between collections. If I want to for example, that in one of the collections I had one cell.

      1. I have 2 CollectionView. The first has only one cell. The second lot. It is necessary to move the cell from the second CollectionView to the first CollectionView with replacement. That is, the first collection of the cell must be replaced. Replaced cell must be transferred to the second collection on the field of movement of the cell. Sorry for my English.

Leave a Reply

Your email address will not be published. Required fields are marked *