Используя длинный жест нажатия, чтобы переупорядочить ячейки в виде таблицы?

Я хочу, чтобы иметь возможность переупорядочивать ячейки таблицы, используя жест longPress (не со стандартными элементами управления переупорядочением). После распознавания longPress я хочу, чтобы tableView по существу перешел в "режим редактирования", а затем изменил порядок, как если бы я использовал элементы управления переупорядочиванием, предоставленные Apple.

Есть ли способ сделать это, не полагаясь на сторонние решения?

Спасибо заранее.

EDIT: я решил использовать решение, которое было в принятом ответе, и полагалось на стороннее решение.

Ответ 1

Итак, вы хотите, чтобы "Clear" -подобное изменение строк не так ли? (около 0:15)

Этот пост SO может помочь.

К сожалению, я не думаю, что вы можете сделать это с помощью существующих инструментов IOS для SDK, не взламывая UITableView + Controller с нуля (вам нужно будет создать каждую строку, и у UITouch будет отвечать соответствующий CGRect вашего строки к ходу).

Это было бы довольно сложно, так как вам нужно заставить анимацию строк "убираться с дороги", когда вы перемещаете строку, которая будет переупорядочена вокруг.

Инструмент кокоса выглядит многообещающим, хотя, по крайней мере, взгляните на источник.

Ответ 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
}

Ответ 3

Вы не можете сделать это с помощью инструментов SDK iOS, если вы не хотите сбрасывать свой собственный UITableView + Controller с нуля, что требует достойного труда. Вы упомянули, что не полагаетесь на сторонние решения, но мой пользовательский класс UITableView может справиться с этим хорошо. Не стесняйтесь проверить:

https://github.com/bvogelzang/BVReorderTableView

Ответ 4

Они добавили способ в 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

приведенный выше код Swift 3 прекрасно работает в Swift 4. Хороший код, спасибо автору! Я внес изменения, чтобы обеспечить работу многосекционной таблицы, подкрепленной основными данными. Поскольку этот код заменяет 'moveRowAt fromIndexPath: IndexPath на toIndexPath: IndexPath', вам необходимо скопировать оттуда код в функцию распознавания длинных нажатий.

Реализуя перемещение строки & обновите код данных в "sender.state ==.changed", который вы обновляете каждый раз. Поскольку я не хотел все эти ненужные обновления основных данных, я переместил код в "sender.state ==.ended". Чтобы это работало, мне нужно было сохранить начальный indexPath в 'sender.state ==.began' и конечный dragInitialIndexPath как toIndexPath.

Ответ 6

Сейчас есть отличная библиотека 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
}

Ответ 7

Конечно, есть способ. Вызовите метод setEditing: animated:, в коде распознавателя жестов, который переведет вид таблицы в режим редактирования. Посмотрите "Управление переустановкой строк" ​​в яблочных документах, чтобы получить дополнительную информацию о перемещении строк.