以前,我尝试用 UITableView 中的长按拖动来替换标准的苹果重新排序控件(从右侧手柄拖动单元格)。然而,由于某种原因,通过长按拖动,我无法将单元格移动到其中已经没有单元格的部分。现在我正在尝试实现一个功能,用户可以在 UICollectionViewController 而不是 UITableView 中的两个部分之间拖动单元格。我实现了长按拖动功能,但由于某种原因我遇到了同样的问题。我如何向这些部分添加一个虚拟单元,以便它们永远不为空,或者是否有更好的方法来解决这个问题?还有一种无需长按即可拖动单元格的方法吗?
这些是我添加到 UICollectionViewController 类中以启用长按拖动的函数:
override func viewDidLoad() {
super.viewDidLoad()
let longPressGesture = UILongPressGestureRecognizer(target: self, action: "handleLongGesture:")
self.collectionView!.addGestureRecognizer(longPressGesture)
}
func handleLongGesture(gesture: UILongPressGestureRecognizer) {
switch(gesture.state) {
case UIGestureRecognizerState.Began:
guard let selectedIndexPath = self.collectionView!.indexPathForItemAtPoint(gesture.locationInView(self.collectionView)) else {
break
}
collectionView!.beginInteractiveMovementForItemAtIndexPath(selectedIndexPath)
case UIGestureRecognizerState.Changed:
collectionView!.updateInteractiveMovementTargetPosition(gesture.locationInView(gesture.view!))
case UIGestureRecognizerState.Ended:
collectionView!.endInteractiveMovement()
default:
collectionView!.cancelInteractiveMovement()
}
}
override func collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
let fromRow = sourceIndexPath.row
let toRow = destinationIndexPath.row
let fromSection = sourceIndexPath.section
let toSection = destinationIndexPath.section
var item: Item
if fromSection == 0 {
item = section1Items[fromRow]
section1Items.removeAtIndex(fromRow)
} else {
item = section2Items[sourceIndexPath.row]
section2Items.removeAtIndex(fromRow)
}
if toSection == 0 {
section1Items.insert(score, atIndex: toRow)
} else {
section2Items.insert(score, atIndex: toRow)
}
}
override func collectionView(collectionView: UICollectionView, canMoveItemAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
谢谢
要首先回答问题的第二部分,请使用 UIPanGestureRecogniser 而不是 UILongPressRecogniser。
对于空部分,您可以将不可见的虚拟单元添加到空部分。在故事板中创建一个没有子视图的原型单元,并确保它具有与集合视图相同的背景颜色。
如果该部分为空,您需要安排显示此单元格。但当在只有 1 个单元格的部分中开始移动时,您还需要添加一个虚拟单元格,否则该部分将在移动过程中折叠,并且用户无法将单元格移回其开始的部分。 在手势处理程序中,开始移动时添加临时单元。如果尚未删除单元格,则在拖动完成时也将其删除(如果单元格实际上并未移动,则不会调用委托 moveItemAtIndexPath 方法):
var temporaryDummyCellPath:NSIndexPath?
func handlePanGesture(gesture: UIPanGestureRecognizer) {
switch(gesture.state) {
case UIGestureRecognizerState.Began:
guard let selectedIndexPath = self.collectionView.indexPathForItemAtPoint(gesture.locationInView(self.collectionView)) else {
break
}
if model.numberOfPagesInSection(selectedIndexPath.section) == 1 {
// temporarily add a dummy cell to this section
temporaryDummyCellPath = NSIndexPath(forRow: 1, inSection: selectedIndexPath.section)
collectionView.insertItemsAtIndexPaths([temporaryDummyCellPath!])
}
collectionView.beginInteractiveMovementForItemAtIndexPath(selectedIndexPath)
case UIGestureRecognizerState.Changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.locationInView(gesture.view!))
case UIGestureRecognizerState.Ended:
collectionView.endInteractiveMovement()
// remove dummy path if not already removed
if let dummyPath = self.temporaryDummyCellPath {
temporaryDummyCellPath = nil
collectionView.deleteItemsAtIndexPaths([dummyPath])
}
default:
collectionView.cancelInteractiveMovement()
// remove dummy path if not already removed
if let dummyPath = temporaryDummyCellPath {
temporaryDummyCellPath = nil
collectionView.deleteItemsAtIndexPaths([dummyPath])
}
}
}
在
collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int)
中,始终返回比模型中项目数多 1 的值,或者如果添加了临时虚拟单元,则返回一个附加单元。
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// special case: about to move a cell out of a section with only 1 item
// make sure to leave a dummy cell
if section == temporaryDummyCellPath?.section {
return 2
}
// always keep one item in each section for the dummy cell
return max(model.numberOfPagesInSection(section), 1)
}
在
collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath)
中,如果该行等于该部分模型中的项目数,则分配虚拟单元。
通过为虚拟单元返回 false 并为其他单元返回 true 来禁用选择
collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath)
中的单元。
同样禁用
collectionView(collectionView: UICollectionView, canMoveItemAtIndexPath indexPath: NSIndexPath)
中的单元格移动确保目标移动路径仅限于模型中的行:
func collectionView(collectionView: UICollectionView, targetIndexPathForMoveFromItemAtIndexPath originalIndexPath: NSIndexPath, toProposedIndexPath proposedIndexPath: NSIndexPath) -> NSIndexPath
{
let proposedSection = proposedIndexPath.section
if model.numberOfPagesInSection(proposedSection) == 0 {
return NSIndexPath(forRow: 0, inSection: proposedSection)
} else {
return proposedIndexPath
}
}
现在你需要处理最后一步。更新您的模型,然后根据需要添加或删除虚拟单元:
func collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath)
{
// move the page in the model
model.movePage(sourceIndexPath.section, fromPage: sourceIndexPath.row, toSection: destinationIndexPath.section, toPage: destinationIndexPath.row)
collectionView.performBatchUpdates({
// if original section is left with no pages, add a dummy cell or keep already added dummy cell
if self.model.numberOfPagesInSection(sourceIndexPath.section) == 0 {
if self.temporaryDummyCellPath == nil {
let dummyPath = NSIndexPath(forRow: 0, inSection: sourceIndexPath.section)
collectionView.insertItemsAtIndexPaths([dummyPath])
} else {
// just keep the temporary dummy we already created
self.temporaryDummyCellPath = nil
}
}
// if new section previously had no pages remove the dummy cell
if self.model.numberOfPagesInSection(destinationIndexPath.section) == 1 {
let dummyPath = NSIndexPath(forRow: 0, inSection: destinationIndexPath.section)
collectionView.deleteItemsAtIndexPaths([dummyPath])
}
}, completion: nil)
}
最后确保虚拟单元没有辅助功能项目,以便在打开语音时跳过它。
func collectionView(_ collectionView: UICollectionView, dragSessionWillBegin session: UIDragSession) {...}
func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession) {...}
开始拖动时,您可以将
临时 单元格添加到空(或所有)部分的末尾。而且你的空部分不会是空的)。然后你可以使用标准 UIKit api 删除你的单元格。在拖动结束之前,您可以删除 临时 单元格。
为什么?我不建议使用手势处理程序,因为:
“制造自行车”的大好机会
更多信息
这是我的解决方案:
func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
guard let destinationIndexPath = coordinator.destinationIndexPath ?? closestIndexPath(coordinator.session, collectionView) else { return }
// Do your reordering work here
}
private func closestIndexPath(_ session: UIDropSession, _ collectionView: UICollectionView) -> IndexPath? {
let location = session.location(in: collectionView)
let indexPaths = collectionView.indexPathsForVisibleSupplementaryElements(ofKind: "SectionHeaderSupplementaryViewIdentifier")
let supplementaryViews = collectionView.visibleSupplementaryViews(ofKind: "SectionHeaderSupplementaryViewIdentifier")
var correctDestination: IndexPath?
collectionView.performUsingPresentationValues {
correctDestination = closestIndexPath(to: location, using: supplementaryViews, with: indexPaths)
}
return correctDestination
}
private func closestIndexPath(to point: CGPoint, using views: [UIView], with indexPaths: [IndexPath]) -> IndexPath? {
guard views.count == indexPaths.count else {
print("Views and indexPaths arrays must have the same count.")
return nil
}
var minDistance: CGFloat = .greatestFiniteMagnitude
var closestIndexPath: IndexPath?
for (index, view) in views.enumerated() {
let distance = abs(point.y - view.frame.maxY)
if distance < minDistance {
minDistance = distance
closestIndexPath = indexPaths[index]
}
}
return closestIndexPath
}