Swift:无法长按拖动单元格到 UICollectionViewController 和 UITableView 中的空白部分

问题描述 投票:0回答:3

以前,我尝试用 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
}

谢谢

ios swift uitableview drag-and-drop uicollectionview
3个回答
4
投票

要首先回答问题的第二部分,请使用 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) }

最后确保虚拟单元没有辅助功能项目,以便在打开语音时跳过它。


4
投票

Xcode 10.2 (10E125)、Swift 5、iOS 11
  • 想法

我想建议处理

func collectionView(_ collectionView: UICollectionView, dragSessionWillBegin session: UIDragSession) {...} func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession) {...}

开始拖动时,您可以将 

临时 单元格添加到空(或所有)部分的末尾。而且你的空部分不会是空的)。然后你可以使用标准 UIKit api 删除你的单元格。在拖动结束之前,您可以删除 临时 单元格。

为什么?

我不建议使用手势处理程序,因为:

“制造自行车”的大好机会
  1. 该解决方案可能非常耗时且维护起来困难/昂贵。
  2. 用于使用经过更深入测试的表/集合的标准 API
  3. 样品

拖放的完整示例

enter image description here更多信息

    支持集合视图中的拖放
  • UICollectionView
  • 拖动委托
  • UICollectionView
  • Drop委托

0
投票

这是我的解决方案:

如果您有节标题(SuplementaryViews),您可以通过查找最接近的 SupplementaryView 及其到当前拖动位置的 IndexPath 来计算正确的 IndexPath:

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 }
© www.soinside.com 2019 - 2024. All rights reserved.