Reputation: 308
I'm making a project with UICollectionView at swift, I need to execute drag-and-drop with each boxes and swap their position. Each boxes has different data and different size, therefore user need to manipulate their layout, for that i need to enable users can drag-n-drop these Views. I wrote a code but when i start drag-n-drop, event will transfer their data instead of boxes itself. How can i solve this?
import UIKit
class ViewController: UIViewController {
var cellIds = ["1","2","3"]
@IBOutlet weak var collectionView: UICollectionView!
fileprivate var longPressGesture = UILongPressGestureRecognizer();
let cellSizes = [
CGSize(width:190, height:200),
CGSize(width:190, height:200),
CGSize(width:190, height:200),
]
@objc func longTap(_ gesture: UIGestureRecognizer){
switch(gesture.state) {
case .began:
guard let selectedIndexPath = collectionView.indexPathForItem(at: gesture.location(in: collectionView)) else {
return
}
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
case .ended:
collectionView.endInteractiveMovement()
//doneBtn.isHidden = false
//longPressedEnabled = true
self.collectionView.reloadData()
default:
collectionView.cancelInteractiveMovement()
}
}
override func viewDidLoad() {
super.viewDidLoad()
longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.longTap(_:)))
collectionView.addGestureRecognizer(longPressGesture)
collectionView!.register(UICustomCollectionViewCell.self, forCellWithReuseIdentifier: "MenuCell")
self.automaticallyAdjustsScrollViewInsets = false
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
//top, left, bottom, right
return UIEdgeInsets(top: 130, left: 1, bottom: 0, right: 10)
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// print("User tapped on \(cellIds[indexPath.row])")
}
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
func userDidEndDragging(_ cell: UICollectionViewCell?) {
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let item = cellIds.remove(at: sourceIndexPath.item)
cellIds.insert(item, at: destinationIndexPath.item)
print(cellIds);
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellIds.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MenuCell", for: indexPath) as? UICustomCollectionViewCell
cell!.layer.cornerRadius=10
cell!.layer.masksToBounds=true
var id = cellIds[indexPath.row];
if (id == "1")
{
cell?.lblTest1.text = "amsterdam";
cell?.lblTest1.tag = 100
}
if (id == "2")
{
cell?.lblTest2.text = "madrid";
cell?.lblTest2.tag = 101
}
if (id == "3")
{
cell?.lblTest3.text = "istanbul";
cell?.lblTest3.tag = 102
}
return cell ?? UICollectionViewCell()
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return cellSizes[indexPath.item]
}
}
import UIKit
class UICustomCollectionViewCell: UICollectionViewCell {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(lblTest1)
self.addSubview(lblTest2)
self.addSubview(lblTest3)
}
var lblTest1:UILabel = {
let label1 = UILabel(frame: CGRect(x:10, y: 20, width: 90 , height: 40))
label1.lineBreakMode = .byWordWrapping
label1.numberOfLines = 0
label1.tag = 100;
label1.tintColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
return label1
}()
var lblTest2:UILabel = {
let label2 = UILabel(frame: CGRect(x:10, y: 20, width: 90 , height: 40))
label2.lineBreakMode = .byWordWrapping
label2.numberOfLines = 0
label2.tag = 101;
label2.tintColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
return label2
}()
var lblTest3:UILabel = {
let label3 = UILabel(frame: CGRect(x:10, y: 20, width: 90 , height: 40))
label3.lineBreakMode = .byWordWrapping
label3.numberOfLines = 0
label3.tag = 102;
label3.tintColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
return label3
}()
override func layoutSubviews() {
super.layoutSubviews()
}
}
Upvotes: -1
Views: 859
Reputation: 2048
Here is how I achieved an elegant drag & drop collectionView:
class FavoritesVC: UIViewController {
//MARK: IBOutlets
@IBOutlet weak var collectionView: UICollectionView!
//MARK: View Model
let viewModel = FavoritesViewModel()
//MARK: Properties
lazy var dragDropFlowLayout: UICollectionViewLayout = {
let flow = DragDropFlowLayout()
flow.scrollDirection = .vertical
flow.headerReferenceSize = CGSize(width: collectionView.frame.width, height: 0.5)
return flow
}()
//MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
viewModel.competitionsUpdateCallback = { [weak self] in
self?.collectionView.reloadData()
}
}
//MARK: Private functions
private func setupCollectionView() {
collectionView.delegate = self
collectionView.dataSource = self
collectionView.collectionViewLayout = dragDropFlowLayout
}
}
//MARK: CollectionView DataSource
extension FavoritesVC: UICollectionViewDataSource {
///...methods for the collectionViewDatasource which aren't relevant here
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
return true
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destination: IndexPath) {
//save the new order of the objects in the data
...
}
}
//MARK: CollectionView Flow Layout Delegate
extension FavoritesVC: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width: collectionView.frame.width / 2 - 15, height: 150)
return size
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 20, left: 10, bottom: 20, right: 10)
}
}
class DragDropFlowLayout: UICollectionViewFlowLayout {
var longPress: UILongPressGestureRecognizer!
var originalIndexPath: IndexPath?
var draggingIndexPath: IndexPath?
var draggingView: UIView?
var dragOffset = CGPoint.zero
override func prepare() {
super.prepare()
installGestureRecognizer()
}
func applyDraggingAttributes(attributes: UICollectionViewLayoutAttributes) {
attributes.alpha = 0
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)
attributes?.forEach { a in
if a.indexPath == draggingIndexPath {
if a.representedElementCategory == .cell {
self.applyDraggingAttributes(attributes: a)
}
}
}
return attributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = super.layoutAttributesForItem(at: indexPath)
if let attributes = attributes, indexPath == draggingIndexPath {
if attributes.representedElementCategory == .cell {
applyDraggingAttributes(attributes: attributes)
}
}
return attributes
}
func installGestureRecognizer() {
if longPress == nil {
longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
longPress.minimumPressDuration = 0.2
collectionView?.addGestureRecognizer(longPress)
}
}
@objc func handleLongPress(_ longPress: UILongPressGestureRecognizer) {
let location = longPress.location(in: collectionView!)
switch longPress.state {
case .began: startDragAtLocation(location: location)
case .changed: updateDragAtLocation(location: location)
case .ended: endDragAtLocation(location: location)
default:
break
}
}
func startDragAtLocation(location: CGPoint) {
guard let cv = collectionView else { return }
guard let indexPath = cv.indexPathForItem(at: location) else { return }
guard cv.dataSource?.collectionView?(cv, canMoveItemAt: indexPath) == true else { return }
guard let cell = cv.cellForItem(at: indexPath) else { return }
originalIndexPath = indexPath
draggingIndexPath = indexPath
draggingView = cell.snapshotView(afterScreenUpdates: true)
draggingView!.frame = cell.frame
cv.addSubview(draggingView!)
dragOffset = CGPoint(x: draggingView!.center.x - location.x, y: draggingView!.center.y - location.y)
draggingView?.layer.shadowPath = UIBezierPath(rect: draggingView!.bounds).cgPath
draggingView?.layer.shadowColor = UIColor.black.cgColor
draggingView?.layer.shadowOpacity = 0.8
draggingView?.layer.shadowRadius = 10
invalidateLayout()
UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.55, initialSpringVelocity: 0, options: [], animations: {
self.draggingView?.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
}, completion: nil)
}
func updateDragAtLocation(location: CGPoint) {
guard let view = draggingView else { return }
guard let cv = collectionView else { return }
view.center = CGPoint(x: location.x + dragOffset.x, y: location.y + dragOffset.y)
if let newIndexPath = cv.indexPathForItem(at: location) {
cv.moveItem(at: draggingIndexPath!, to: newIndexPath)
draggingIndexPath = newIndexPath
}
}
func endDragAtLocation(location: CGPoint) {
guard let dragView = draggingView else { return }
guard let indexPath = draggingIndexPath else { return }
guard let cv = collectionView else { return }
guard let datasource = cv.dataSource else { return }
let targetCenter = datasource.collectionView(cv, cellForItemAt: indexPath).center
UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.55, initialSpringVelocity: 0, options: [], animations: {
dragView.center = targetCenter
dragView.transform = .identity
dragView.layer.shadowColor = UIColor.white.cgColor
dragView.layer.shadowOpacity = 0
dragView.layer.shadowRadius = 0
}) { (completed) in
if indexPath != self.originalIndexPath! {
datasource.collectionView?(cv, moveItemAt: self.originalIndexPath!, to: indexPath)
}
dragView.removeFromSuperview()
self.draggingIndexPath = nil
self.draggingView = nil
self.invalidateLayout()
}
}
}
Upvotes: 0