UICollectionView 拖放单元格
Posted
技术标签:
【中文标题】UICollectionView 拖放单元格【英文标题】:UICollectionView Drag and Drop cell 【发布时间】:2019-10-16 07:24:09 【问题描述】:您好,我想使用 uicollectionview 进行拖放。执行拖放时,它正在移动内容,我想像照片中那样做。我想让盒子自己携带。例如;当我将照片拖到 4 时,我应该以完整的尺寸离开红色区域。将照片 6 交换到照片 1,就像将照片 3 放在左侧一样。我在uicollectionview中研究了很多,但我找不到这样的东西。请帮帮我
import UIKit
final class ViewController: UIViewController
@IBOutlet weak var collectionView: UICollectionView!
var cellIds = ["image 1","image 2","image 3","image 4","image 5","6","7"]
override func viewDidLoad()
super.viewDidLoad()
let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
flowLayout.estimatedItemSize = CGSize(width: 200, height: 10)
let gestureRecognizer = UILongPressGestureRecognizer(target: self,
action: #selector(self.handleLongPress(gestureRecognizer:)))
collectionView.addGestureRecognizer(gestureRecognizer)
@objc func handleLongPress(gestureRecognizer: UILongPressGestureRecognizer)
guard let view = gestureRecognizer.view else return
let location = gestureRecognizer.location(in: view)
switch gestureRecognizer.state
case .began:
guard let selectedIndexPath = collectionView.indexPathForItem(at: location) else break
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case .changed:
collectionView.updateInteractiveMovementTargetPosition(location)
case .ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
extension ViewController: UICollectionViewDataSource,
UICollectionViewDelegateFlowLayout
func numberOfSections(in collectionView: UICollectionView) -> Int
return 1
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
return cellIds.count
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SampleCell", for: indexPath) as! SampleCell
let text = cellIds[indexPath.item]
cell.label.text = text
return cell
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool
return true
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)
let text = cellIds.remove(at: sourceIndexPath.item)
cellIds.insert(text, at: destinationIndexPath.item)
collectionView.reloadData()
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
if (indexPath.row==0)
return CGSize(width: 190, height: 100)
if (indexPath.row==1)
return CGSize(width: 190, height: 100)
if (indexPath.row==2)
return CGSize(width: 190, height: 400)
if (indexPath.row==3)
return CGSize(width: 400, height: 200)
return CGSize(width: 0, height: 0)
final class SampleCell: UICollectionViewCell
@IBOutlet weak var label: UILabel!
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes
setNeedsLayout()
layoutIfNeeded()
let size = self.systemLayoutSizeFitting(layoutAttributes.size)
var newFrame = layoutAttributes.frame
// note: don't change the width
newFrame.size.height = ceil(size.height)
layoutAttributes.frame = newFrame
return layoutAttributes
picture
【问题讨论】:
我认为仅使用 1 个集合视图是不可能完成的。这可能需要对彼此靠近的几个 collectionView 进行复杂的设置,然后处理 collectionView 之间的拖放。我的建议:想出一个更简单的设计,否则这将是一个非常复杂的处理方法。 【参考方案1】:你可以使用UICollectionViewDragDelegate
,
对于多个section,为了拖到最后,我们应该在拖拽的时候多加一个item。
示例代码:
视图控制器:
import UIKit
enum CellModel
case simple(text: String)
case availableToDropAtEnd
class SecondController: UIViewController
private lazy var cellIdentifier = "cellIdentifier"
private lazy var supplementaryViewIdentifier = "supplementaryViewIdentifier"
private lazy var sections = 10
private lazy var itemsInSection = 2
private lazy var numberOfElementsInRow = 3
private lazy var data: [[CellModel]] =
var count = 0
return (0 ..< sections).map _ in
return (0 ..< itemsInSection).map _ -> CellModel in
count += 1
return .simple(text: "cell \(count)")
()
override func viewDidLoad()
super.viewDidLoad()
let collectionViewFlowLayout = UICollectionViewFlowLayout()
collectionViewFlowLayout.minimumLineSpacing = 5
collectionViewFlowLayout.minimumInteritemSpacing = 5
let _numberOfElementsInRow = CGFloat(numberOfElementsInRow)
let allWidthBetwenCells = _numberOfElementsInRow == 0 ? 0 : collectionViewFlowLayout.minimumInteritemSpacing*(_numberOfElementsInRow-1)
let width = (view.frame.width - allWidthBetwenCells)/_numberOfElementsInRow
collectionViewFlowLayout.itemSize = CGSize(width: width, height: width)
collectionViewFlowLayout.headerReferenceSize = CGSize(width: 0, height: 40)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewFlowLayout)
collectionView.backgroundColor = .white
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
collectionView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
collectionView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: cellIdentifier)
collectionView.register(SupplementaryView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: supplementaryViewIdentifier)
collectionView.dragInteractionEnabled = true
collectionView.reorderingCadence = .fast
collectionView.dropDelegate = self
collectionView.dragDelegate = self
collectionView.delegate = self
collectionView.dataSource = self
extension SecondController: UICollectionViewDelegate
extension SecondController: UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int
return data.count
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
return data[section].count
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! CollectionViewCell
switch data[indexPath.section][indexPath.item]
case .simple(let text):
cell.label?.text = text
cell.backgroundColor = .gray
case .availableToDropAtEnd:
cell.backgroundColor = UIColor.green.withAlphaComponent(0.3)
return cell
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: supplementaryViewIdentifier, for: indexPath)
extension SecondController: UICollectionViewDragDelegate
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem]
let itemProvider = NSItemProvider(object: "\(indexPath)" as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = data[indexPath.section][indexPath.row]
return [dragItem]
func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem]
let itemProvider = NSItemProvider(object: "\(indexPath)" as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = data[indexPath.section][indexPath.row]
return [dragItem]
func collectionView(_ collectionView: UICollectionView, dragSessionWillBegin session: UIDragSession)
var itemsToInsert = [IndexPath]()
(0 ..< data.count).forEach
itemsToInsert.append(IndexPath(item: data[$0].count, section: $0))
data[$0].append(.availableToDropAtEnd)
collectionView.insertItems(at: itemsToInsert)
func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession)
var removeItems = [IndexPath]()
for section in 0..<data.count
for item in 0..<data[section].count
switch data[section][item]
case .availableToDropAtEnd:
removeItems.append(IndexPath(item: item, section: section))
case .simple:
break
removeItems.forEach data[$0.section].remove(at: $0.item)
collectionView.deleteItems(at: removeItems)
extension SecondController: UICollectionViewDropDelegate
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator)
let destinationIndexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath
destinationIndexPath = indexPath
else
// useless, just in case
let section = collectionView.numberOfSections - 1
let row = collectionView.numberOfItems(inSection: section)
destinationIndexPath = IndexPath(row: row, section: section)
switch coordinator.proposal.operation
case .move:
reorderItems(coordinator: coordinator, destinationIndexPath:destinationIndexPath, collectionView: collectionView)
default:
break
func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool
return true
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal
// made the above logic useless
if collectionView.hasActiveDrag, destinationIndexPath != nil
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
else
return UICollectionViewDropProposal(operation: .forbidden)
private
func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView)
let items = coordinator.items
if items.count == 1, let item = items.first,
let sourceIndexPath = item.sourceIndexPath,
let localObject = item.dragItem.localObject as? CellModel
collectionView.performBatchUpdates (
data[sourceIndexPath.section].remove(at: sourceIndexPath.item)
data[destinationIndexPath.section].insert(localObject, at: destinationIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIndexPath])
)
查看:
import UIKit
class CollectionViewCell: UICollectionViewCell
weak var label: UILabel?
override init(frame: CGRect)
super.init(frame: frame)
clipsToBounds = true
let label = UILabel(frame: .zero)
label.contentMode = .scaleAspectFill
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
label.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
label.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
label.textAlignment = .center
label.textColor = .white
self.label = label
layer.borderWidth = 1
layer.borderColor = UIColor.white.cgColor
backgroundColor = .white
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
override func prepareForReuse()
super.prepareForReuse()
label?.text = nil
backgroundColor = .white
class SupplementaryView: UICollectionReusableView
override init(frame: CGRect)
super.init(frame: frame)
backgroundColor = UIColor.blue.withAlphaComponent(0.7)
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
github link
【讨论】:
嗨,我有字典数组。我能用那个数组做到这一点吗?我知道如何拖动字符串数组。但我对此并不熟悉。你能帮帮我吗 当然。下载每个示例代码,运行并学习以上是关于UICollectionView 拖放单元格的主要内容,如果未能解决你的问题,请参考以下文章
如何在 UICollectionView 拖放期间删除“幽灵”单元格,并使移动单元格不透明?
在具有 UITableView 的 UICollectionView 中拖放
将 SKProduct 价格提取到 UICollectionView 单元格标签中
UICollectionView 与 SwiftUI + 拖放重新排序可能吗?