UICollectionView 快速拖放
Posted
技术标签:
【中文标题】UICollectionView 快速拖放【英文标题】:UICollectionView Drag-N-Drop at swift 【发布时间】:2019-10-11 06:19:33 【问题描述】:我正在快速使用 UICollectionView 制作一个项目,我需要对每个框执行拖放操作并交换它们的位置。每个盒子都有不同的数据和不同的大小,因此用户需要操纵他们的布局,因为我需要让用户可以拖放这些视图。我写了一个代码,但是当我开始拖放时,事件将传输他们的数据而不是盒子本身。我该如何解决这个问题?
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()
【问题讨论】:
发布您的代码。可见的代码比文字更清楚)) 我已经编辑了我的帖子 我建议你看看这个resource。这里不是“1个简单的答案”,而是教你如何制作自定义的“DragDropFlowLayout”。我实现了这个资源的拖放流布局示例,并使用了 collectionView 中的标准方法来实现美观流畅的“拖放”功能。您将需要更多的自定义,因为您说过要拖放不同大小的单元格。祝你好运! 你能把你的 Swift4 代码发给我们吗?你能帮我们吗? 【参考方案1】:这是我如何实现优雅的拖放 collectionView:
我有 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 =
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()
【讨论】:
以上是关于UICollectionView 快速拖放的主要内容,如果未能解决你的问题,请参考以下文章
collectionView代理方法快速设置cell大小上下左右间隔