Используя длинный жест для изменения порядка ячеек в табличном представлении?
Я хочу иметь возможность изменить порядок ячеек таблицы с помощью жеста longPress (не со стандартными элементами управления переупорядочением). После того, как longPress распознан, я хочу, чтобы tableView по существу перешел в "режим редактирования", а затем изменил порядок, как если бы я использовал элементы управления изменением порядка, предоставленные Apple.
Есть ли способ сделать это, не полагаясь на сторонние решения?
Заранее спасибо.
РЕДАКТИРОВАТЬ: я закончил тем, что использовал решение, которое было в принятом ответе и полагалось на стороннее решение.
5 ответов
Так что, по сути, вы хотите, чтобы "Clear"-подобная строка переупорядочивалась, верно? (около 0:15)
К сожалению, я не думаю, что вы можете сделать это с помощью существующих инструментов iOS SDK, не взломав вместе UITableView + Controller с нуля (вам нужно создать каждую строку самостоятельно и иметь ответ UITouch, соответствующий CGRect вашей строки для -переехать).
Это было бы довольно сложно, так как вам нужно получить анимацию строк, "уходящих с дороги", когда вы перемещаете ряд, который будет переупорядочен.
Хотя инструмент Cocoas выглядит многообещающе, по крайней мере, посмотрите на источник.
Они добавили способ в 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) {
}
}
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
}
Вы не можете сделать это с помощью инструментов iOS SDK, если не хотите собрать свой собственный UITableView + Controller с нуля, который требует приличного количества работы. Вы упомянули, что не полагаетесь на сторонние решения, но мой пользовательский класс UITableView может с этим справиться. Не стесняйтесь проверить это:
Там есть большая библиотека 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
}
Приведенный выше код Swift 3 прекрасно работает в Swift 4. Хороший код, спасибо автору! Я внес изменения, чтобы обеспечить работу многосекционной таблицы, подкрепленной основными данными. Поскольку этот код заменяет 'moveRowAt fromIndexPath: IndexPath на toIndexPath: IndexPath', вам необходимо скопировать оттуда код в функцию распознавания длинных нажатий.
Внедряя строку перемещения и обновляя код данных в 'sender.state == .changed', вы обновляете каждый раз. Поскольку я не хотел все эти ненужные обновления основных данных, я переместил код в "sender.state == .ended". Чтобы это работало, мне нужно было сохранить начальный indexPath в 'sender.state == .began' и конечный dragInitialIndexPath как toIndexPath.
Конечно, есть способ. Вызовите метод setEditing:animated:, в вашем коде распознавателя жестов, который переведет представление таблицы в режим редактирования. Посмотрите "Управление переупорядочением строк" в документах Apple, чтобы получить больше информации о перемещении строк.