UICollectionView 与 SwiftUI + 拖放重新排序可能吗?
Posted
技术标签:
【中文标题】UICollectionView 与 SwiftUI + 拖放重新排序可能吗?【英文标题】:UICollectionView with SwiftUI + Drag and drop reordering possible? 【发布时间】:2020-05-22 10:31:16 【问题描述】:我正在我的 SwiftUI 视图中实现一个 UICollectionView(也与 SwiftUI 视图单元格)。 所以我有一个 Hosting+Representable 组合。
现在我想通过拖放重新排列我的单元格,但没有任何反应。
这个想法是将longpressGesture与给定的函数canMoveItemAt和moveItemAt一起使用。
这里是完整的代码:
import SwiftUI
import UIKit
struct ContentView: View
var body: some View
CollectionComponent()
struct CollectionComponent : UIViewRepresentable
func makeCoordinator() -> CollectionComponent.Coordinator
Coordinator(data: [])
class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegate
var data: [String] = []
init(data: [String])
for index in (0...20)
self.data.append("\(index)")
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
data.count
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! Cell
cell.cellView.rootView = AnyView(CellView(text: data[indexPath.item]))
return cell
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool
return true
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)
print("Changing the cell order, moving: \(sourceIndexPath.row) to \(destinationIndexPath.row)")
func makeUIView(context: Context) -> UICollectionView
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: 150, height: 150)
layout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = .white
collectionView.dataSource = context.coordinator
collectionView.delegate = context.coordinator
collectionView.register(Cell.self, forCellWithReuseIdentifier: "cell")
let longPressGesture = UILongPressGestureRecognizer(target: self, action: Selector(("handleLongGesture:")))
collectionView.addGestureRecognizer(longPressGesture)
func handleLongGesture(gesture: UILongPressGestureRecognizer)
switch(gesture.state)
case UIGestureRecognizerState.began:
guard let selectedIndexPath = collectionView.indexPathForItem(at: gesture.location(in: collectionView)) else
break
collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case UIGestureRecognizerState.changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
case UIGestureRecognizerState.ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
return collectionView
func updateUIView(_ uiView: UICollectionView, context: Context)
class Cell: UICollectionViewCell
public var cellView = UIHostingController(rootView: AnyView(EmptyView()))
public override init(frame: CGRect)
super.init(frame: frame)
configure()
public required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
configure()
private func configure()
contentView.addSubview(cellView.view)
cellView.view.preservesSuperviewLayoutMargins = false
cellView.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
cellView.view.leftAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leftAnchor),
cellView.view.rightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.rightAnchor),
cellView.view.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
cellView.view.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor),
])
struct CellView: View
let text: String
var body: some View
ZStack
Text(text)
.frame(width: 150, height: 150)
.background(Color.blue)
谢谢!
【问题讨论】:
使用LazyGrid
的完整SwiftUI版本可以在这里找到:***.com/questions/62606907/…
【参考方案1】:
只需使用UICollectionViewDragDelegate
和UICollectionViewDropDelegate
将单元格视图拖放到UICollectionView
中。它完美地工作。这是示例代码...
struct ContentView: View
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var body: some View
GeometryReader proxy in
GridView(self.numbers, proxy: proxy) number in
Image("image\(number)")
.resizable()
.scaledToFill()
struct GridView<CellView: View>: UIViewRepresentable
let cellView: (Int) -> CellView
let proxy: GeometryProxy
var numbers: [Int]
init(_ numbers: [Int], proxy: GeometryProxy, @ViewBuilder cellView: @escaping (Int) -> CellView)
self.proxy = proxy
self.cellView = cellView
self.numbers = numbers
func makeUIView(context: Context) -> UICollectionView
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
let collectionView = UICollectionView(frame: UIScreen.main.bounds, collectionViewLayout: layout)
collectionView.backgroundColor = .white
collectionView.register(GridCellView.self, forCellWithReuseIdentifier: "CELL")
collectionView.dragDelegate = context.coordinator //to drag cell view
collectionView.dropDelegate = context.coordinator //to drop cell view
collectionView.dragInteractionEnabled = true
collectionView.dataSource = context.coordinator
collectionView.delegate = context.coordinator
collectionView.contentInset = UIEdgeInsets(top: 4, left: 4, bottom: 4, right: 4)
return collectionView
func updateUIView(_ uiView: UICollectionView, context: Context)
func makeCoordinator() -> Coordinator
Coordinator(self)
class Coordinator: NSObject, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDragDelegate, UICollectionViewDropDelegate
var parent: GridView
init(_ parent: GridView)
self.parent = parent
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
return parent.numbers.count
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CELL", for: indexPath) as! GridCellView
cell.backgroundColor = .clear
cell.cellView.rootView = AnyView(parent.cellView(parent.numbers[indexPath.row]).fixedSize())
return cell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
return CGSize(width: ((parent.proxy.frame(in: .global).width - 8) / 3), height: ((parent.proxy.frame(in: .global).width - 8) / 3))
//Provides the initial set of items (if any) to drag.
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem]
let item = self.parent.numbers[indexPath.row]
let itemProvider = NSItemProvider(object: String(item) as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
//Tells your delegate that the position of the dragged data over the collection view changed.
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal
if collectionView.hasActiveDrag
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
return UICollectionViewDropProposal(operation: .forbidden)
//Tells your delegate to incorporate the drop data into the collection view.
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator)
var destinationIndexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath
destinationIndexPath = indexPath
else
let row = collectionView.numberOfItems(inSection: 0)
destinationIndexPath = IndexPath(item: row - 1, section: 0)
if coordinator.proposal.operation == .move
self.reorderItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
private func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView)
if let item = coordinator.items.first, let sourceIndexPath = item.sourceIndexPath
collectionView.performBatchUpdates(
self.parent.numbers.remove(at: sourceIndexPath.item)
self.parent.numbers.insert(item.dragItem.localObject as! Int, at: destinationIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIndexPath])
, completion: nil)
coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
class GridCellView: UICollectionViewCell
public var cellView = UIHostingController(rootView: AnyView(EmptyView()))
public override init(frame: CGRect)
super.init(frame: frame)
configure()
public required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
configure()
private func configure()
contentView.addSubview(cellView.view)
cellView.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
cellView.view.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 5),
cellView.view.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -5),
cellView.view.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5),
cellView.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5),
])
cellView.view.layer.masksToBounds = true
你可以在这里看到最终结果https://media.giphy.com/media/UPuWLauQepwi5Q77PA/giphy.gif 谢谢。 X_X
【讨论】:
嘿,非常感谢您帮助我!这是有效的——唯一的问题是感觉一切都有延迟?你也有这个问题吗?不幸的是,它不够流畅:/ 抱歉回复晚了。是的,我遇到了同样的问题。但是使用UIKit View
代替SwiftUI View
来代替cell view
没有任何延迟。
这里是只使用UIKit View
media.giphy.com/media/JmyxP7KF1qPLx8k45M/giphy.gif的结果以上是关于UICollectionView 与 SwiftUI + 拖放重新排序可能吗?的主要内容,如果未能解决你的问题,请参考以下文章
UICollectionView 与 UIWebView 大小问题
UIScrollView 与 UICollectionView