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

时间:2012-09-04 04:55:13

标签: ios uitableview long-press

我希望能够使用longPress手势(不使用标准重新排序控件)重新排序tableview单元格。在识别出longPress之后,我希望tableView基本上进入“编辑模式”,然后重新排序,好像我正在使用Apple提供的重新排序控件。

有没有办法在不需要依赖第三方解决方案的情况下做到这一点?

提前致谢。

编辑:我最终使用了已接受答案中的解决方案并依赖于第三方解决方案。

7 个答案:

答案 0 :(得分:17)

Swift 3,没有第三方解决方案

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

var dragInitialIndexPath: IndexPath?
var dragCellSnapshot: UIView?

然后将UILongPressGestureRecognizer添加到您的tableView

let longPress = UILongPressGestureRecognizer(target: self, action: #selector(onLongPressGesture(sender:)))
longPress.minimumPressDuration = 0.2 // optional
tableView.addGestureRecognizer(longPress)

处理UILongPressGestureRecognizer

// 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
}

答案 1 :(得分:8)

你不能用iOS SDK工具来做,除非你想从头开始把你自己的UITableView + Controller放在一起,这需要大量的工作。你提到不依赖第三方解决方案,但我的自定义UITableView类可以很好地处理这个问题。随意查看:

https://github.com/bvogelzang/BVReorderTableView

答案 2 :(得分:3)

基本上你想要"Clear"-like row reordering对吗? (约0:15)

This SO post might help.

不幸的是,我不认为你可以用现在的iOS SDK工具做到这一点,而不是从头开始一起攻击UITableView + Controller(你需要自己创建每一行并让UITouch响应与你的CGRect相关)行到移动)。

这很复杂,因为当你移动要重新排序的行时,你需要让行的动画“偏离”。

cocoas工具看起来很有希望,至少可以看看来源。

答案 3 :(得分:1)

上面的Swift 3代码在Swift 4中工作正常。感谢作者! 我进行了更改,以使由核心数据支持的多节表能够正常工作。 由于此代码代替了'moveRowAt fromIndexPath:IndexPath到toIndexPath:IndexPath',因此您需要从那里将代码复制到长按识别器函数中。

通过在'sender.state == .changed'中实现移动行和更新数据代码,您每次都会更新。因为我不想所有那些不必要的核心数据更新,所以将代码移到了“ sender.state == .ended”中。为了使其能够正常工作,我必须将初始indexPath存储在'sender.state == .began'中,并将最终的dragInitialIndexPath存储为toIndexPath。

答案 4 :(得分:0)

他们在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) {
    }
}

答案 5 :(得分:-1)

现在有一个很棒的Swift库,名为SwiftReorder,它是MIT许可的,因此您可以将其用作第一方解决方案。该库的基础是它使用UITableView扩展名将控制器对象注入到符合TableViewReorderDelegate的任何表视图中:

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
}

答案 6 :(得分:-2)

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