Written for Swift 4.0, it is an implementation of Dragging and Dropping data across multiple UICollectionViews.
Try it on Appetize.io!
- iOS 8.0+
- XCode 9.0+
- Swift 4.0 +
pod 'KDDragAndDropCollectionViews', '~> 1.3'
Add the files in Classes/
to your project.
Make the UICollectionView of interest a KDDragAndDropCollectionView
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
}
Apart form the dataSource protocol that is implemented by the controller who owns the data that feeds the collection view, there are two more protocols of interest: KDDraggable
and KDDroppable
. These are implemented directly by the KDDragAndDropCollectionView
and account for a finer control over what gets dragged where.
The easier way to access them is to subclass KDDragAndDropCollectionView
and overide the default implementation. For example, we might not want to drop a particular item from the view:
class MyCV: KDDragAndDropCollectionView {
override func canDropAtRect(_ rect: CGRect) -> Bool {
let condition = /* custom logic that results to (for example) ... */ false
return super.canDropAtRect(rect) && condition
}
}
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.