【问题标题】:Using long press gesture to reorder cells in tableview?使用长按手势重新排序表格视图中的单元格?
【发布时间】:2012-08-28 17:32:29
【问题描述】:

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

有没有办法做到这一点而无需依赖 3rd 方解决方案?

提前致谢。

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

【问题讨论】:

  • 嘿,我也在尝试这样做。你最终使用了什么?
  • 上面的 Swift 3 代码在 Swift 4 中运行良好。代码很好,感谢作者!我进行了更改以启用由核心数据支持的多节表。由于此代码取代了 'moveRowAt fromIndexPath: IndexPath, toIndexPath: IndexPath',您需要将代码从那里复制到长按识别器功能中。通过在 'sender.state == .changed' 中实现移动行和更新数据代码,您每次都在更新。由于我不希望所有这些不必要的核心数据更新,我将代码移动到“sender.state == .ended”。为了使它能够工作,我必须存储初始

标签: ios uitableview long-press


【解决方案1】:

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

【讨论】:

  • 效果很好,只要记住在 func moveRowAt 中为你的数组添加新索引 let item = myArray[sourceIndexPath.row] myArray.remove(at: sourceIndexPath.row) myArray.insert(item,在:destinationIndexPath.row)
  • 即使没有设置 tableview.dropDelegate 和声明 dropSessionDidUpdate 也可以工作。
  • 这是正确、最快和最简单的解决方案。第一次工作正确。谢谢你展示这个。不需要第三方库或愚蠢的长代码。
  • 效果很好,但我收到一个奇怪的错误:'PBItemCollectionServicer connection disconnected'
【解决方案2】:

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
}

【讨论】:

  • 当我尝试使用此代码时应用程序崩溃并出现错误:第 0 部分中的行数无效。更新后现有部分中包含的行数必须等于行数更新前包含在该部分中
  • 我用我的数据源上的 exchangeObject 方法解决了这个崩溃,比如 dataSource.exchangeObject(at: dragInitialIndexPath!.row, withObjectAt: indexPath!.row)
  • 发布代码的绝对图例。做得好。工作安静。
  • 完美解决方案
  • 简单的解决方案就像一个魅力。
【解决方案3】:

除非您想从头开始将自己的 UITableView + Controller 组合在一起,否则您无法使用 iOS SDK 工具来做到这一点,这需要大量的工作。您提到不依赖 3rd 方解决方案,但我的自定义 UITableView 类可以很好地处理这个问题。请随意查看:

https://github.com/bvogelzang/BVReorderTableView

【讨论】:

    【解决方案4】:

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

    This SO post might help.

    不幸的是,我认为您无法使用当前的 iOS SDK 工具来完成它,除非您从头开始将 UITableView + Controller 组合在一起(您需要自己创建每一行并让 UITouch 响应与您的 CGRect 相关的行移动)。

    这会非常复杂,因为您需要在移动要重新排序的行时让行的动画“让开”。

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

    【讨论】:

    • 是的,这正是我想要的重新排序类型,谢谢。我希望避免第 3 方解决方案或从头开始“一起破解”的东西,但在这种情况下可能是不可避免的。
    【解决方案5】:

    现在有一个很棒的 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
    }
    

    【讨论】:

    • 在“无需依赖 3rd 方解决方案”的问题中确实很清楚地表明了这一点
    • 我在谷歌搜索中找到了这个问题,我在不抄袭源代码的情况下解释了项目是如何组合在一起的。所以,1) 对于那些希望解决这个问题的人来说,它仍然是一个相关的答案,并且 2) 考虑到源代码是 MIT 许可的,人们可以直接在他们的项目中使用该代码并使其成为第一方。
    • 这是我找到的最佳解决方案。我已经尝试过 iOS11 Drag 委托,并说它有问题和丑陋是轻描淡写的。 SwiftReorder 远比 Apple 自己的重新排序控制效果更好。这是一个很棒的图书馆。
    【解决方案6】:

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

    【讨论】:

    • 是的,这会起作用,但是我希望它是一个动作而不是两个动作。我想跳过重新排序控件出现的需要以及需要​​使用它们重新排序单元格的用户。应该是这样的:LongPress 启动编辑模式并允许用户拖动单元格而无需抬起手指并按下重新排序控件。
    • jemicha,您能找到解决方案吗?我希望完成同样的任务。
    • 我正在寻找相同的内容(进入编辑模式并长按开始重新排序),您有什么线索吗?请
    猜你喜欢
    • 2012-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多