Ios 使用长按手势对tableview中的单元格重新排序?

Ios 使用长按手势对tableview中的单元格重新排序?,ios,uitableview,long-press,Ios,Uitableview,Long Press,我希望能够使用长按手势对tableview单元格进行重新排序(而不是使用标准的重新排序控件)。在longPress被识别后,我希望tableView基本上进入“编辑模式”,然后像使用Apple提供的重新排序控件一样重新排序 有没有一种不需要依赖第三方解决方案就能做到这一点的方法 提前谢谢 编辑:我最终使用了公认答案中的解决方案,并依赖于第三方解决方案。当然有办法。在手势识别器代码中调用方法setEditing:animated:,该方法将使表格视图进入编辑模式。在apple文档中查找“管理行的重

我希望能够使用长按手势对tableview单元格进行重新排序(而不是使用标准的重新排序控件)。在longPress被识别后,我希望tableView基本上进入“编辑模式”,然后像使用Apple提供的重新排序控件一样重新排序

有没有一种不需要依赖第三方解决方案就能做到这一点的方法

提前谢谢


编辑:我最终使用了公认答案中的解决方案,并依赖于第三方解决方案。

当然有办法。在手势识别器代码中调用方法setEditing:animated:,该方法将使表格视图进入编辑模式。在apple文档中查找“管理行的重新排序”,以获得有关移动行的更多信息。

那么基本上您想要正确的?(大约0:15)

不幸的是,我不认为你能用现有的iOS SDK工具做到这一点,除非从零开始拼凑出一个UITableView+控制器(你需要自己创建每一行,并有一个与要移动的行的CGRect相关的UITouch响应)

这将是相当复杂的,因为在移动要重新排序的行时,需要获得行“让路”的动画


cocoas工具看起来很有前途,至少去看看它的源代码。

你不能用iOS SDK工具来实现它,除非你想从头开始组装你自己的UITableView+控制器,这需要相当多的工作。您提到不依赖第三方解决方案,但我的自定义UITableView类可以很好地处理这个问题。请随意查看:


Swift 3,无第三方解决方案

首先,将这两个变量添加到类中:

var dragInitialIndexPath: IndexPath?
var dragCellSnapshot: UIView?
然后将
ui长按手势识别器
添加到您的
表格视图

let longPress = UILongPressGestureRecognizer(target: self, action: #selector(onLongPressGesture(sender:)))
longPress.minimumPressDuration = 0.2 // optional
tableView.addGestureRecognizer(longPress)
Handle
ui长按手势识别器

// MARK: cell reorder / long press

func onLongPressGesture(sender: UILongPressGestureRecognizer) {
  let locationInView = sender.location(in: tableView)
  let indexPath = tableView.indexPathForRow(at: locationInView)

  if sender.state == .began {
    if indexPath != nil {
      dragInitialIndexPath = indexPath
      let cell = tableView.cellForRow(at: indexPath!)
      dragCellSnapshot = snapshotOfCell(inputView: cell!)
      var center = cell?.center
      dragCellSnapshot?.center = center!
      dragCellSnapshot?.alpha = 0.0
      tableView.addSubview(dragCellSnapshot!)

      UIView.animate(withDuration: 0.25, animations: { () -> Void in
        center?.y = locationInView.y
        self.dragCellSnapshot?.center = center!
        self.dragCellSnapshot?.transform = (self.dragCellSnapshot?.transform.scaledBy(x: 1.05, y: 1.05))!
        self.dragCellSnapshot?.alpha = 0.99
        cell?.alpha = 0.0
      }, completion: { (finished) -> Void in
        if finished {
          cell?.isHidden = true
        }
      })
    }
  } else if sender.state == .changed && dragInitialIndexPath != nil {
    var center = dragCellSnapshot?.center
    center?.y = locationInView.y
    dragCellSnapshot?.center = center!

    // to lock dragging to same section add: "&& indexPath?.section == dragInitialIndexPath?.section" to the if below
    if indexPath != nil && indexPath != dragInitialIndexPath {
      // update your data model
      let dataToMove = data[dragInitialIndexPath!.row]
      data.remove(at: dragInitialIndexPath!.row)
      data.insert(dataToMove, at: indexPath!.row)

      tableView.moveRow(at: dragInitialIndexPath!, to: indexPath!)
      dragInitialIndexPath = indexPath
    }
  } else if sender.state == .ended && dragInitialIndexPath != nil {
    let cell = tableView.cellForRow(at: dragInitialIndexPath!)
    cell?.isHidden = false
    cell?.alpha = 0.0
    UIView.animate(withDuration: 0.25, animations: { () -> Void in
      self.dragCellSnapshot?.center = (cell?.center)!
      self.dragCellSnapshot?.transform = CGAffineTransform.identity
      self.dragCellSnapshot?.alpha = 0.0
      cell?.alpha = 1.0
    }, completion: { (finished) -> Void in
      if finished {
        self.dragInitialIndexPath = nil
        self.dragCellSnapshot?.removeFromSuperview()
        self.dragCellSnapshot = nil
      }
    })
  }
}

func snapshotOfCell(inputView: UIView) -> UIView {
  UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0.0)
  inputView.layer.render(in: UIGraphicsGetCurrentContext()!)
  let image = UIGraphicsGetImageFromCurrentImageContext()
  UIGraphicsEndImageContext()

  let cellSnapshot = UIImageView(image: image)
  cellSnapshot.layer.masksToBounds = false
  cellSnapshot.layer.cornerRadius = 0.0
  cellSnapshot.layer.shadowOffset = CGSize(width: -5.0, height: 0.0)
  cellSnapshot.layer.shadowRadius = 5.0
  cellSnapshot.layer.shadowOpacity = 0.4
  return cellSnapshot
}

现在有一个很棒的Swift库,叫做MIT许可的,所以你可以把它作为第一方解决方案。此库的基础是它使用
UITableView
扩展将控制器对象注入符合
tableviewerorderedelegate
的任何表视图:

extension UITableView {

    private struct AssociatedKeys {
        static var reorderController: UInt8 = 0
    }

    /// An object that manages drag-and-drop reordering of table view cells.
    public var reorder: ReorderController {
        if let controller = objc_getAssociatedObject(self, &AssociatedKeys.reorderController) as? ReorderController {
            return controller
        } else {
            let controller = ReorderController(tableView: self)
            objc_setAssociatedObject(self, &AssociatedKeys.reorderController, controller, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            return controller
        }
    }

}
然后代理看起来有点像这样:

public protocol TableViewReorderDelegate: class {

    // A series of delegate methods like this are defined:
    func tableView(_ tableView: UITableView, reorderRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)

}
控制器如下所示:

public class ReorderController: NSObject {

    /// The delegate of the reorder controller.
    public weak var delegate: TableViewReorderDelegate?

    // ... Other code here, can be found in the open source project

}

实现的关键是,在触摸点显示快照单元格时,有一个“间隔单元格”插入到表视图中,因此您需要在
cellForRow:atIndexPath:
调用中处理间隔单元格:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let spacer = tableView.reorder.spacerCell(for: indexPath) {
        return spacer
    }
    // otherwise build and return your regular cells
}

他们在iOS 11中添加了一种方法

首先,启用拖动交互并设置拖放代理

然后实现moveRowAt,就像使用“重新排序”控件正常移动单元格一样

然后实现拖放代理,如下所示

tableView.dragInteractionEnabled = true
tableView.dragDelegate = self
tableView.dropDelegate = self

func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { }

extension TableView: UITableViewDragDelegate {
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        return [UIDragItem(itemProvider: NSItemProvider())]
    }
} 

extension TableView: UITableViewDropDelegate {
    func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {

        if session.localDragSession != nil { // Drag originated from the same app.
            return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
        }

        return UITableViewDropProposal(operation: .cancel, intent: .unspecified)
    }

    func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
    }
}


是的,这将工作,但我希望它是一个行动,而不是两个。我想跳过显示重新排序控件的需要,以及用户需要使用它们来重新排序单元格的需要。应该是这样的:LongPress启动编辑模式,允许用户拖动单元格,而无需抬起手指并按下重新排序控件。jemicha,你能找到解决方案吗?我期待着完成同样的任务。我期待着同样的(进入编辑模式,并在一个长按开始重新排序),你是如何做到这一点的线索?请注意,这正是我想要的重新排序类型,谢谢。我希望避免第三方解决方案或从头开始的“黑客攻击”,但在这种情况下,这可能是不可避免的。你最终使用了什么?上面的Swift 3代码在Swift 4中运行良好。很好的代码,感谢作者!我做了一些更改,以使由核心数据支持的多节表能够工作。由于此代码取代了“moveRowAt fromIndexPath:IndexPath到toIndexPath:IndexPath”,因此需要将代码从那里复制到长按识别器函数中。通过在“sender.state==.changed”中实现移动行和更新数据代码,您每次都在更新。由于我不希望所有那些不必要的核心数据更新,我将代码移到了“sender.state==.ended”。要使其工作,我必须存储初始值。当我尝试使用此代码时,应用程序正在崩溃,错误为:节0中的行数无效。更新后现有节中包含的行数必须等于更新前该节中包含的行数我在我的数据源(如datasource.exchangeObject)上使用exchangeObject方法解决此崩溃。exchangeObject(at:dragInitialIndexPath!.row,withObjectAt:indexPath!.row)绝对图例发布代码。做得好。很好地解决了这个问题。完美的解决方案简单的解决方案很有魅力。它在问题“不需要依赖第三方解决方案”中说得很清楚。我在谷歌搜索中发现了这个问题,我费劲地解释了这个项目是如何在不抄袭源代码的情况下组合起来的。因此,1)对于那些希望解决这个问题的人来说,这仍然是一个相关的答案,2)考虑到源代码是MIT许可的,人们可以在他们的项目中直接使用代码,并将其作为第一方。这是我找到的最佳解决方案。我试过iOS11拖动代理,说它是油嘴滑舌和丑陋是轻描淡写的。与苹果自己的再订购控制相比,SwiftReorder的效果要好得多。这是一个非常好的库。它非常好用,只需记住在func moveRowAt中为数组添加新索引,让item=myArray[sourceindexath.row]myArray.remove(at:sourceindexath.row)myArray.insert(item,at:destinationindexath.row)即使不设置tableview.dropDelegate和声明dropSessionDidUpdate,这也可以工作。这是正确、最快和最简单的解决方案。第一次就成功了。谢谢你展示这个。不需要第三方库或愚蠢的长代码。它工作得很好,但我得到了一个奇怪的er