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
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
A mirror of Apple's sample code for high performance collection views in iOS 15.

Building High-Performance Lists and Collection Views Improve the performance of lists and collections in your app with prefetching and image preparati

Tim Oliver 14 Nov 12, 2022
Blueprints is a collection of flow layouts that is meant to make your life easier when working with collection view flow layouts.

Blueprints is a collection of flow layouts that is meant to make your life easier when working with collection view flow layouts. It comes

Christoffer Winterkvist 982 Dec 7, 2022
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
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
BouncyLayout is a collection view layout that makes your cells bounce.

BouncyLayout is a collection view layout that makes your cells bounce. Features Pure Swift 5. Works with every UICollectionView. Horizontal and vertic

Robert-Hein Hooijmans 4.2k Jan 5, 2023
CardsLayout is a lightweight Collection Layout.

CardsLayout is a lightweight Collection Layout. Installation CocoaPods

Filipp Fediakov 798 Dec 28, 2022
An easy-to-use Collection View Layout for card-like animation.

CarLensCollectionViewLayout An easy-to-use Collection View Layout for card-like animation ?? CarLensCollectionViewLayout was created out of the implem

Netguru 530 Dec 16, 2022
Gliding Collection is a smooth, flowing, customizable decision for a UICollectionView Swift Controller.

A smooth, flowing, customizable decision for a UICollectionView Swift Controller We specialize in the designing and coding of custo

Ramotion 1.5k Dec 19, 2022
UICollectionViewSplitLayout makes collection view more responsive.

UICollectionViewSplitLayout makes collection view more responsive. What's this? UICollectionViewSplitLayout is a subclass of UICollectionViewLayout. I

Yahoo! JAPAN 239 Dec 6, 2022
Declaretive UICollectionViewCompositionalLayout interface to implement complex collection view layout.

CompositionalLayoutViewController Example To run the example project, clone the repo, and run pod install from the Example directory first. Requiremen

ONEinc 19 Dec 2, 2022
Adding ruby style each iterator to Cocoa/Cocoa touch Swift Array and Range classes, And Int.times{} to Int class

Collection-Each Adding ruby style each iterator to Cocoa/Cocoa touch Swift Array, Dictionary and Range classes, and Int.times ###Why? Array/Dictionary

Omar Abdelhafith 65 Jun 9, 2018
A lightweight UICollectionViewLayout that 'pages' and centers its cells 🎡 written in Swift

CenteredCollectionView CenteredCollectionView is a lightweight drop in place UICollectionViewFlowLayout that pages and keeps its cells centered, resul

Ben Emdon 1.2k Jan 6, 2023
UICollectionViewCell with checkbox when it isSelected and empty circle when not - like Photos.app "Select" mode.

CheckmarkCollectionViewCell UICollectionViewCell with checkbox when it isSelected and empty circle when not - like Photos.app "Select" mode. Usage cla

Yonat Sharon 62 Oct 19, 2022
↕️ VegaScroll is a lightweight animation flowlayout for UICollectionView completely written in Swift 4, compatible with iOS 11 and Xcode 9.

Made by Applikey Solutions Find this project on Dribbble Also check another flowlayout for UICollectionView: https://github.com/ApplikeySolutions/Grav

Applikey Solutions 2.8k Jan 1, 2023
UICollectionView and UIStackView Homework

UICollectionView and UIStackView Homework Use one outer StackView containing one CollectionView on top and a horizontal StackView at the bottom with t

null 0 Nov 11, 2021
This component allows for the transfer of data items between collection views through drag and drop

Drag and Drop Collection Views Written for Swift 4.0, it is an implementation of Dragging and Dropping data across multiple UICollectionViews. Try it

Michael Michailidis 508 Dec 19, 2022
Drag & drop to reorder items in SwiftUI.

SwiftUIReorderableForEach Easily implement drag & drop to reorder items in SwiftUI. This package contains a generic ReoderableForEach component, which

Gordan Glavaš 7 Dec 15, 2022
List a collection of items in a horizontally scrolling view. A scaling factor controls the size of the items relative to the center.

CAROUSEL List a collection of items in a horizontally scrolling view. A scaling factor controls the size of the items relative to the center. We speci

Ramotion 557 Dec 31, 2022
Drag and drop between your apps in split view mode on iOS 9

SplitViewDragAndDrop Easily add drag and drop to pass data between your apps Setup Add pod 'SplitViewDragAndDrop' to your Podfile or copy the "SplitVi

Mario Iannotta 324 Nov 22, 2022