This component allows for the transfer of data items between collection views through drag and drop

Overview

Drag and Drop Collection Views

Written for Swift 4.0, it is an implementation of Dragging and Dropping data across multiple UICollectionViews.

Drag and Drop Illustration

Try it on Appetize.io!

Language Licence CocoaPods Awesome

Requirements

  • iOS 8.0+
  • XCode 9.0+
  • Swift 4.0 +

Installation

Cocoa Pods

pod 'KDDragAndDropCollectionViews', '~> 1.5.2'

Manual

Add the files in Classes/ to your project.

Quick Guide

Make the UICollectionView of interest a KDDragAndDropCollectionView

XCode Interface Builder Screen

Then set a class as dataSource implementing the KDDragAndDropCollectionViewDataSource protocol.

class ViewController: UIViewController, KDDragAndDropCollectionViewDataSource {

    @IBOutlet weak var firstCollectionView: KDDragAndDropCollectionView!
    @IBOutlet weak var secondCollectionView: KDDragAndDropCollectionView!
    @IBOutlet weak var thirdCollectionView: KDDragAndDropCollectionView!
    
    var data : [[DataItem]] = [[DataItem]]() // just for this example
    
    var dragAndDropManager : KDDragAndDropManager?
    
    override func viewDidLoad() {
        let all = [firstCollectionView, secondCollectionView, thirdCollectionView]
        self.dragAndDropManager = KDDragAndDropManager(canvas: self.view, collectionViews: all)
    }
}

The only responsibility of the user code is to manage the data that the collection view cells are representing. The data source of the collection views must implement the KDDragAndDropCollectionViewDataSource protocol.

In the example we have 3 UICollectionViews distinguishable by their tags (bad practice, I know... but it's only an example ;-) and a data array holding 3 arrays respectively. In a case like this, an implementation of the above could be:

func collectionView(collectionView: UICollectionView, dataItemForIndexPath indexPath: NSIndexPath) -> AnyObject {
    return data[collectionView.tag][indexPath.item]
}

func collectionView(collectionView: UICollectionView, insertDataItem dataItem : AnyObject, atIndexPath indexPath: NSIndexPath) -> Void {
    if let di = dataItem as? DataItem {
        data[collectionView.tag].insert(di, atIndex: indexPath.item)
    }
}

func collectionView(collectionView: UICollectionView, deleteDataItemAtIndexPath indexPath : NSIndexPath) -> Void {
    data[collectionView.tag].removeAtIndex(indexPath.item)
}

func collectionView(collectionView: UICollectionView, moveDataItemFromIndexPath from: NSIndexPath, toIndexPath to : NSIndexPath) -> Void {
    let fromDataItem: DataItem = data[collectionView.tag][from.item]
    data[collectionView.tag].removeAtIndex(from.item)
    data[collectionView.tag].insert(fromDataItem, atIndex: to.item)    
}

func collectionView(_ collectionView: UICollectionView, indexPathForDataItem dataItem: AnyObject) -> IndexPath? {

    guard let candidate = dataItem as? DataItem else { return nil }
    
    for (i,item) in data[collectionView.tag].enumerated() {
        if candidate != item { continue }
        return IndexPath(item: i, section: 0)
    }
    return nil
}

Advanced Use

Prevent specific Items from being Dragged and/or Dropped

For a finer tuning on what items are draggable and which ones are not we can implement the following function from the KDDragAndDropCollectionViewDataSource protocol

func collectionView(_ collectionView: UICollectionView, cellIsDraggableAtIndexPath indexPath: IndexPath) -> Bool {
    return indexPath.row % 2 == 0
}

Data Items and Equatable

In the example code included in this project, I have created a DataItem class to represent the data displayed by the collection view.

class DataItem : Equatable {
    var indexes: String
    var colour: UIColor
    init(indexes: String, colour: UIColor = UIColor.clear) {
        self.indexes    = indexes
        self.colour     = colour
    }
    static func ==(lhs: DataItem, rhs: DataItem) -> Bool {
        return lhs.indexes == rhs.indexes && lhs.colour == rhs.colour
    }
}

In the course of development you will be making your own types that must comform to the Equatable protocol as above. Each data item must be uniquely idenfyiable so be careful when creating cells that can have duplicate display values as for example a "Scrabble" type game where the same letter appears more than once. In cases like these, a simple identifier will do to implement the equality.

Comments
  • Dynamic cell size breaks dragging

    Dynamic cell size breaks dragging

    I configured my collection view cells to calculate their height by overriding UICollectionViewCell.preferredLayoutAttributesFitting.

    When I do this, dragging appears to break in a weird way. When I first drag an item from one collection view to another, the items in the collection view I'm dragging to all move down to make room for the cell I'm dragging, even though the cell I'm dragging barely even entered the other collection view. After about a second, the cells in the collection view I'm dragging to all re-arrange and go back to normal. At that point, I can drag the new cell into it's position.

    Any ideas on what this may be? I've spent the last two days trying to figure it out...

    It seems to be something in the KDDragAndDropCollectionView.didMoveItem function.

    Thanks.

    opened by danielchristopher1 16
  • Fast drag and drop outside

    Fast drag and drop outside

    Hello! At first, I would say thank you for that awesome extension, very helpful for my project. I downloaded your examples, and probably found a bug, and I even tried to fix that, but because I don't have a lot of experience, couldn't do that. If in collectionView function just remove "if draggingPathOfCellBeingDragged.item == indexPath.item {cell.hidden = true}" it doesn't have this bug, but looks not so good.

    Problem: When a try to drag and drop easy, everything looks good, but when I do it so much faster and do like drop outside, cell exist in new view, but hidden. For more understanding I record a video for you. Please watch this video on https://www.dropbox.com/s/rtq0pj1771v8txg/kddragfail.mov?dl=0

    opened by ModisDone 14
  • Error when dragging cell out of collectionView

    Error when dragging cell out of collectionView

    For some reason when I implemented the exact same files into my own project, I am getting this issue whenever I drag a cell out of a collection view:

    ‘Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (21) must be equal to the number of items contained in that section before the update (21), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).’

    I set up the storyboard the exact same way as the example project, but I can't seem to figure out this issue. Thanks, Neil

    opened by neilsekhon 7
  • Bug on same item that is swap.

    Bug on same item that is swap.

    Hi,

    I used your library. Great library by the way. I found a bug. For example i have array of [A,B, C, D, A] when i swap A to A, the whole cell moves. Is there any solution for this? i just wanted to swap A to A. i am working on a word puzzle.

    Thanks!

    opened by zuil7 6
  • KDDragAndDropCollectionView inheritance

    KDDragAndDropCollectionView inheritance

    Is ti possible to change access modifier for class KDDragAndDropCollectionView from 'public' to 'open', to provide the possibility of inheritance from KDDragAndDropCollectionView?

    opened by alednik 3
  • how to prevent to  collectionview drag

    how to prevent to collectionview drag

    hello i have two collectionview user can drag from one collectionview to Second collectionview but how to prevent from second collectionview to one collectionview thanks

    enhancement 
    opened by kirti301290 3
  • Remove Item

    Remove Item

    Hello Michael, It would be great if you could help me with adding new function in your project. I need to place a UIButton inside the CollectionViewCell, that will remove self Cell with existing animation; same like when dragging and dropping items.

    Now I realized that just by placing function in UITableViewController class and UIButton with "addTarget", but It hasn't your animation.

    help wanted 
    opened by ModisDone 3
  • Added can drag cell at index path datasource method

    Added can drag cell at index path datasource method

    Something like this if you wanted every other cell to be draggable. Implemented this way in the test app.

    func collectionView(_ collectionView: UICollectionView, cellIsDraggableAtIndexPath indexPath: IndexPath) -> Bool {
        return indexPath.row % 2 == 0
    }
    
    opened by twof 2
  • inside ScrollView implementation

    inside ScrollView implementation

    Hi There, Thanks for awesome extension and your great work!

    I am implementing this for my UITableView where each cell is a CollectionView, however KDDragAndDropManager only works for the initial visible cells, If I scroll and load new cells, those collectionViews will not be droppable.

    Can you suggest a place that I can look deeper to address the issue.

    Cheers

    opened by ghost 2
  • Change pointOnCanvas source view

    Change pointOnCanvas source view

    This enables the use of custom gesture recognizers that have the KDDragAndDropManager as delegate and updateForLongPress as target.

    In my use, I needed to perform some extra actions when dragging items between collection views (disabling the scrollView, recognizing taps quicker than the default recognizer in the module).

    My custom gesture recognizer was added to a collectionView itself, and this has resulted in the wrong frame of representation image while dragging the item, since recogniser.view == a subview of self.canvas.

    This change does not break anything, since the default recognizer in the module is added to self.canvas anyway, and at the same time it provides the user with an ability to use their own recognizers.

    opened by marcingalus 1
  • Programmatically create collection views

    Programmatically create collection views

    I've recently discovered this library and would really love to work with it. I downloaded the example project and it works like a charm! Unfortunately, I can neither use storyboards for my case (as the number of collection views needs to be adjusted to the number of arrays in my data) nor do I have a lot of time due to my next semester starting next week (and therefore being a little stressed). (Did I miss some non-storyboard code for the collection views? If so, please let me know.)

    Anyway, for 2 days now, I tried to implement this library such that it takes my data and creates as many collection views as arrays in my data (= array of arrays of ProgrammaticallyDragAndDropItems). I've tried a lot at this point but can not at all put my finger on why I am constantly getting the same error (EXC_BAD_ACCESS at performBatchUpdates in KDDragAndDropCollectionView.swift) whenever I try to drag one of the cells to another position - the app crashes immediately. If nothing is moved and the cell remains at its previous position, however, nothing happens, i.e. the recognition of the right cell (and the item's index in the data if I print it in the console) works fine.

    This is what invariably happens:

    Screenshot 2019-09-28 at 00 33 51

    I know you probably don't have a lot of time but I'd really appreciate if you could take a glance at my code and tell me what I should try or what you think could be the problem since I'm really frustrated at this point. It's very close to the code in the example. I'll post it below and focus on the important things first. I hope I am not missing something obvious. Please bear with me 😬

    Here is the extension for the collection views first (below is the class of the controller & the item):

    // cvIndex = index of the collection view in the array of KDDragAndDropCollectionViews (instead of tags)
    extension ProgrammaticallyDragAndDropController: KDDragAndDropCollectionViewDataSource, UICollectionViewDelegateFlowLayout {
        
        // UICollectionViewDataSource
        
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            
            guard dragAndDropItems.count != 0, let cvIndex = kdCollectionViews.firstIndex(where: { $0 == collectionView }) else { return 0 }
            
            return dragAndDropItems[cvIndex].count
            
        }
        
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
            guard let cvIndex = kdCollectionViews.firstIndex(where: { $0 == collectionView }), let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kdCollectionViewsIDs[cvIndex], for: indexPath) as? ProgrammaticallyDragAndDropItemCell else {
                fatalError("fatal: cell for item at index path")
            }
    
            cell.programmaticallyDragAndDropItem = dragAndDropItems[cvIndex][indexPath.item]
    
            cell.isHidden = false
    
            if let kdCollectionView = collectionView as? KDDragAndDropCollectionView {
    
                if let draggingPathOfCellBeingDragged = kdCollectionView.draggingPathOfCellBeingDragged {
    
                    cell.isHidden = draggingPathOfCellBeingDragged.item == indexPath.item
    
                }
    
            }
            
            return cell
            
        }
        
        
        // added by KDDragAndDropCollectionViewDataSource
        
        func collectionView(_ collectionView: UICollectionView, dataItemForIndexPath indexPath: IndexPath) -> AnyObject {
            
            guard let cvIndex = kdCollectionViews.firstIndex(where: { $0 == collectionView }) else { fatalError("fatal: data item for index path") }
            print("data item for cvindex indexpath", cvIndex, indexPath.item)
            
            return dragAndDropItems[cvIndex][indexPath.item]
            
        }
        
        func collectionView(_ collectionView: UICollectionView, insertDataItem dataItem: AnyObject, atIndexPath indexPath: IndexPath) {
            
            if let cvIndex = kdCollectionViews.firstIndex(where: { $0 == collectionView }), let dragAndDropItem = dataItem as? ProgrammaticallyDragAndDropItem {
                dragAndDropItems[cvIndex].insert(dragAndDropItem, at: indexPath.item)
            }
            
        }
        
        func collectionView(_ collectionView: UICollectionView, deleteDataItemAtIndexPath indexPath: IndexPath) {
            
            if let cvIndex = kdCollectionViews.firstIndex(where: { $0 == collectionView }) {
                dragAndDropItems[cvIndex].remove(at: indexPath.item)
            }
            
        }
        
        func collectionView(_ collectionView: UICollectionView, moveDataItemFromIndexPath from: IndexPath, toIndexPath to: IndexPath) {
            
            if let cvIndex = kdCollectionViews.firstIndex(where: { $0 == collectionView }) {
                let dragAndDropItem = dragAndDropItems[cvIndex][from.item]
                dragAndDropItems[cvIndex].remove(at: from.item)
                dragAndDropItems[cvIndex].insert(dragAndDropItem, at: to.item)
            }
            
        }
        
        func collectionView(_ collectionView: UICollectionView, indexPathForDataItem dataItem: AnyObject) -> IndexPath? {
            
            if let candidate = dataItem as? ProgrammaticallyDragAndDropItem, let cvIndex = kdCollectionViews.firstIndex(where: { $0 == collectionView }) {
                
                for (index, item) in dragAndDropItems[cvIndex].enumerated() {
                    if candidate != item { continue }
                    return IndexPath(item: index, section: 0)
                }
                
            }
            
            return nil
            
        }
        
        func collectionView(_ collectionView: UICollectionView, cellIsDroppableAtIndexPath indexPath: IndexPath) -> Bool {
            return true
        }
    
        func collectionView(_ collectionView: UICollectionView, cellIsDraggableAtIndexPath indexPath: IndexPath) -> Bool {
            return true
        }
    
        func collectionView(_ collectionView: UICollectionView, stylingRepresentationView: UIView) -> UIView? {
            return nil
        }
        
    }
    

    This is the class (which probably isn't especially important):

    import UIKit
    import Stevia
    import KDDragAndDropCollectionViews
    
    class ProgrammaticallyDragAndDropController: UIViewController {
        
        var kdManager: KDDragAndDropManager?
        var dragAndDropItems = [
            [ProgrammaticallyDragAndDropItem(id: "id1", name: "name1", belongsTo: "group1"), ProgrammaticallyDragAndDropItem(id: "id2", name: "name2", belongsTo: "group1"), ProgrammaticallyDragAndDropItem(id: "id3", name: "name3", belongsTo: "group1")],
            [ProgrammaticallyDragAndDropItem(id: "id4", name: "name4", belongsTo: "group2"), ProgrammaticallyDragAndDropItem(id: "id5", name: "name5", belongsTo: "group2"), ProgrammaticallyDragAndDropItem(id: "id6", name: "name6", belongsTo: "group2")],
            [ProgrammaticallyDragAndDropItem(id: "id7", name: "name7", belongsTo: "group3"), ProgrammaticallyDragAndDropItem(id: "id8", name: "name8", belongsTo: "group3"), ProgrammaticallyDragAndDropItem(id: "id9", name: "name9", belongsTo: "group3")]
        ]
        
        var kdCollectionViews = [KDDragAndDropCollectionView]()
        var kdCollectionViewsIDs = [String]()
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            setupViews()
    
            kdManager = KDDragAndDropManager(canvas: view, collectionViews: kdCollectionViews)
        }
        
        private func setupViews() {
                            
            // fixme: for loop -> number of groups = number of collection views
            // in this case 3 collection views (created below)
            let layout = UICollectionViewFlowLayout()
            layout.scrollDirection = .horizontal
    
            let testCVs = [KDDragAndDropCollectionView(frame: .zero, collectionViewLayout: layout), KDDragAndDropCollectionView(frame: .zero, collectionViewLayout: layout), KDDragAndDropCollectionView(frame: .zero, collectionViewLayout: layout)]
            let testCVIDs = ["cellID0", "cellID1", "cellID2"]
    
            kdCollectionViews.append(contentsOf: testCVs)
            kdCollectionViewsIDs.append(contentsOf: testCVIDs)
            
            let safeView = UIView()
            
            view.sv(
                safeView.sv(
                    kdCollectionViews
                )
            )
            
            safeView.fillHorizontally()
            safeView.Top == view.safeAreaLayoutGuide.Top
            safeView.Bottom == view.safeAreaLayoutGuide.Bottom
            
            for (index, kdCollectionView) in kdCollectionViews.enumerated() {
                
                kdCollectionView.dataSource = self
                kdCollectionView.delegate = self
                kdCollectionView.register(ProgrammaticallyDragAndDropItemCell.self, forCellWithReuseIdentifier: kdCollectionViewsIDs[index])
                
                // layout with Stevia (just sugar, it's the same as constraints)
                kdCollectionView.fillHorizontally()
                kdCollectionView.height(100)
    
                kdCollectionView.backgroundColor = .red
                
                // attach to top of safe view (safe area insets) if first cv
                if index == 0 {
                    kdCollectionView.Top == safeView.Top
                    
                // otherwise attach 100 points below previous cv
                } else {
                    kdCollectionView.Top == kdCollectionViews[index - 1].Bottom + 100
                }
    
            }
            
        }
        
    }
    

    The cell is pretty much a UICollectionViewCell with another background color right now.

    This is my item class:

    class ProgrammaticallyDragAndDropItem: Equatable {
        
        var id: String
        var name: String
        var belongsTo: String
        
        init(id: String, name: String, belongsTo: String) {
            self.id = id
            self.name = name
            self.belongsTo = belongsTo
        }
        
        static func == (lhs: ProgrammaticallyDragAndDropItem, rhs: ProgrammaticallyDragAndDropItem) -> Bool {
            return lhs.id == rhs.id && lhs.name == rhs.name && lhs.belongsTo == rhs.belongsTo
        }
        
    }
    
    opened by blurtime 1
  • [bug] Unexpected UI behavior when dragging

    [bug] Unexpected UI behavior when dragging

    I am unsure if this is an iOS 13 issue, a modal view controller presentation issue, or an issue with the source code, but the collection view cells go crazy when I am dragging and dropping:

    https://youtu.be/42lfaF0GHU0

    This is quite frustrating for the user because cells are not dropped in the place the pan gesture ends and it takes multiple tries to achieve the desired result and sometimes the desired result is impossible to achieve.

    opened by Ludotrico 1
Owner
Michael Michailidis
iOS + Node.js = ❤
Michael Michailidis
A custom paging behavior that peeks the previous and next items in a collection view

MSPeekCollectionViewDelegateImplementation Version 3.0.0 is here! ?? The peeking logic is now done using a custom UICollectionViewLayout which makes i

Maher Santina 353 Dec 16, 2022
Custom transition between two collection view layouts

Display Switcher We designed a UI that allows users to switch between list and grid views on the fly and choose the most convenient display type. List

Yalantis 2.3k Dec 14, 2022
Easy and type-safe iOS table and collection views in Swift.

Quick Start TL;DR? SimpleSource is a library that lets you populate and update table views and collection views with ease. It gives you fully typed cl

Squarespace 96 Dec 26, 2022
TLIndexPathTools is a small set of classes that can greatly simplify your table and collection views.

TLIndexPathTools TLIndexPathTools is a small set of classes that can greatly simplify your table and collection views. Here are some of the awesome th

SwiftKick Mobile 347 Sep 21, 2022
Modern Collection Views

The goal is to showcase different compositional layouts and how to achieve them. Feel free to use any code you can find and if you have interesting layout idea - open PR!

Filip Němeček 536 Dec 28, 2022
Reusable iOS's behavior drag or swipe to pop ViewController

DLSwipeToPopController Reusable iOS's behavior to pop ViewController base on SwipeRightToPopController: Swipe from Right to Left to pop ViewController

Le Ngoc Duy 1 Sep 17, 2022
Generic collection view controller with external data processing

FlexibleCollectionViewController Swift library of generic collection view controller with external data processing of functionality, like determine ce

Dmytro Pylypenko 3 Jul 16, 2018
An iOS drop-in UITableView, UICollectionView and UIScrollView superclass category for showing a customizable floating button on top of it.

MEVFloatingButton An iOS drop-in UITableView, UICollectionView, UIScrollView superclass category for showing a customizable floating button on top of

Manuel Escrig 298 Jul 17, 2022
🚴 A declarative library for building component-based user interfaces in UITableView and UICollectionView.

A declarative library for building component-based user interfaces in UITableView and UICollectionView. Declarative Component-Based Non-Destructive Pr

Ryo Aoyama 1.2k Jan 5, 2023
Dwifft is a small Swift library that tells you what the "diff" is between two collections

Dwifft! In 10 seconds Dwifft is a small Swift library that tells you what the "diff" is between two collections, namely, the series of "edit operation

Jack Flintermann 1.8k Dec 12, 2022
A Swift mixin for reusing views easily and in a type-safe way (UITableViewCells, UICollectionViewCells, custom UIViews, ViewControllers, Storyboards…)

Reusable A Swift mixin to use UITableViewCells, UICollectionViewCells and UIViewControllers in a type-safe way, without the need to manipulate their S

Olivier Halligon 2.9k Jan 3, 2023
Easy way to integrate pagination with dummy views in CollectionView, make Instagram "Discover" within minutes.

AZCollectionView Controller Features Automatic pagination handling No more awkward empty CollectionView screen AZ CollectionVIew controller give you a

Afroz Zaheer 95 May 11, 2022
A SwiftUI collection view with support for custom layouts, preloading, and more.

ASCollectionView A SwiftUI implementation of UICollectionView & UITableView. Here's some of its useful features: supports preloading and onAppear/onDi

Apptek Studios 1.3k Dec 24, 2022
Lightweight custom collection view inspired by Airbnb.

ASCollectionView Lightweight custom collection view inspired by Airbnb. Screenshots Requirements ASCollectionView Version Minimum iOS Target Swift Ver

Abdullah Selek 364 Nov 24, 2022
A data-driven UICollectionView framework for building fast and flexible lists.

A data-driven UICollectionView framework for building fast and flexible lists. Main Features ?? Never call performBatchUpdates(_:, completion:) or rel

Instagram 12.5k Jan 1, 2023
PJFDataSource is a small library that provides a simple, clean architecture for your app to manage its data sources while providing a consistent user interface for common content states (i.e. loading, loaded, empty, and error).

PJFDataSource PJFDataSource is a small library that provides a simple, clean architecture for your app to manage its data sources while providing a co

Square 88 Jun 30, 2022
A generic small reusable components for data source implementation for UITableView/UICollectionView in Swift.

GenericDataSource A generic small reusable components for data source implementation for UITableView/UICollectionView written in Swift. Features Basic

null 132 Sep 8, 2021
💾 🔜📱 Type-safe data-driven CollectionView, TableView Framework. (We can also use ASCollectionNode)

⚠️ The latest updates is this PR. It changes the difference algorithm to DifferenceKit. DataSources ?? ?? ?? Type-safe data-driven List-UI Framework.

Muukii 563 Dec 16, 2022
Conv smart represent UICollectionView data structure more than UIKit.

Conv Conv smart represent UICollectionView data structure more than UIKit. Easy definition for UICollectionView DataSource and Delegate methods. And C

bannzai 157 Nov 25, 2022