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

Posted

技术标签:

【中文标题】Swift:无法长按拖动单元格到 UICollectionViewController 和 UITableView 中的空白部分【英文标题】:Swift: Unable to long-press drag cells to empty sections in UICollectionViewController and UITableView 【发布时间】:2016-06-25 13:21:05 【问题描述】:

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

谢谢

【问题讨论】:

你有没有想过这个问题? @Weston 我最终使用了这个 cocoapod github.com/mmick66/KDDragAndDropCollectionView 【参考方案1】:

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


最后确保虚拟单元格没有可访问性项目,以便在打开画外音时跳过它。

【讨论】:

【参考方案2】:

详情

Xcode 10.2 (10E125),Swift 5,来自 ios 11

想法

我想建议处理

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

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

为什么?

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

    “制造自行车”的大好机会 该解决方案可能非常耗时且维护困难/昂贵。 用于处理经过深入测试的表/集合的标准 API

示例

Full sample of drag and drop

更多信息

Supporting Drag and Drop in Collection Views UICollectionViewDragDelegate UICollectionViewDropDelegate

【讨论】:

以上是关于Swift:无法长按拖动单元格到 UICollectionViewController 和 UITableView 中的空白部分的主要内容,如果未能解决你的问题,请参考以下文章

Swift基础--手势识别(双击捏旋转拖动划动长按)

调整从所选单元格到特定范围的Excel选择

tvOS tableView 从一个单元格到另一个单元格平滑滚动

如何使用viewForSupplementaryElementOfKind - swift显示UICollectionView的特定单元格

选择从第一个单元格到列中使用的最后一个单元格的范围

Swift:如何将 backgroundView 添加到不会被拖动的表格单元格?