Home > Net >  UITableView Not Showing Reorder Controls
UITableView Not Showing Reorder Controls

Time:04-12

When I use trailingSwipeActionsConfigurationForRowAt my TableView will show the delete and reorder options, however when selecting reorder nothing happens. I think I have all of the correct methods and am calling setEditing; is there anything else I'm missing? Thanks!

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

  let tableView = UITableView()

  override func viewDidLoad() {
      super.viewDidLoad()
    
      setupTableView()
  }

  func setupTableView() {
      tableView.frame = self.view.frame
      tableView.dataSource = self
      tableView.delegate = self
      tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
      tableView.dragInteractionEnabled = true
    
      self.view.addSubview(tableView)
  }

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
       return 8
  }

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
      cell.backgroundColor = .gray
      cell.showsReorderControl = true

      return cell
  }

  override func setEditing(_ editing: Bool, animated: Bool) {
      super.setEditing(editing, animated: animated)
      self.tableView.setEditing(editing, animated: animated)
  }

  func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
      let deleteAction = UIContextualAction(style: .normal, title: "delete") { (action, view, completion) in
          tableView.reloadData()
          completion(true)
      }
    
      let reorderAction = UIContextualAction(style: .normal, title: "reorder") { (action, view, completion) in
          tableView.setEditing(true, animated: true)
          completion(true)
      }

      return UISwipeActionsConfiguration(actions: [deleteAction, reorderAction])
   }
}

class CustomCell: UITableViewCell {
 
}

Result after swiping:

enter image description here

After selecting reorder:

enter image description here

CodePudding user response:

A few observations:

  1. You are not going to get the reorder controls if you do not implement tableView(_:moveRowAt:to:), e.g., assuming you had a model which was an array called objects, you could do the following:

    func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        let object = objects.remove(at: sourceIndexPath.row)
        objects.insert(object, at: destinationIndexPath.row)
    }
    
  2. The trailingSwipeActionsConfigurationForRowAt is probably not the right place to put a “reorder” command. Part of the reason is that once the table view is in edit mode and you tap on the ⛔️, the trailing actions show up, and “reorder” does not make sense in that context. E.g., here I am tapping on ⛔️ and I see the confusing actions.

    enter image description here

    I would suggest only adding “delete” as the trailing action. That way, you (a) get only “delete” if you tap on ⛔️ in isEditing mode, but also (b) get the stand-alone swipe action, too.

  3. You cannot initiate isEditing from the trailing swipe actions (and, as discussed above, I do not think you want to, anyway). So, if you do not have “reorder” in the trailing swipe actions, you need some other method to enter edit mode. E.g., above, I added an “edit” button to the navigation bar that toggles isEditing:

    @IBAction func didTapEdit(_ sender: Any) {
        tableView.isEditing.toggle()
    }
    

    Then, you can keep the swipe to delete functionality, but when you tap on edit button, you have the tap on ⛔️ to delete functionality (plus the handles for reordering because we added tableView(_:moveRowAt:to:) as outlined in step one, above):

    enter image description here

  4. Another way to achieve reordering is to just allow drag and drop within the table view where you can long-press on a row and then drag it:

    enter image description here

    This is enabled by setting dragInteractionEnabled and dropDelegate:

    class ViewController: UIViewController {
        @IBOutlet weak var tableView: UITableView!
    
        let formatter: NumberFormatter = {
            let formatter = NumberFormatter()
            formatter.numberStyle = .spellOut
            return formatter
        }()
    
        private var objects: [Foo] = ...
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            ...
    
            tableView.dragInteractionEnabled = true
            tableView.dropDelegate = self
        }
    }
    
    // MARK: - UITableViewDataSource
    
    extension ViewController: UITableViewDataSource { ... }
    
    // MARK: - UITableViewDelegate
    
    extension ViewController: UITableViewDelegate {
        func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
            let deleteAction = UIContextualAction(style: .destructive, title: "delete") { [weak self] action, view, completion in
                self?.objects.remove(at: indexPath.row)
                tableView.deleteRows(at: [indexPath], with: .middle)
                completion(true)
            }
    
            return UISwipeActionsConfiguration(actions: [deleteAction])
        }
    
        // This is used if table view is in `isEditing` mode and by `UITableViewDropDelegate`
    
        func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
            let object = objects.remove(at: sourceIndexPath.row)
            objects.insert(object, at: destinationIndexPath.row)
        }
    }
    
    // MARK: - UITableViewDropDelegate
    
    extension ViewController: UITableViewDropDelegate {
        func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
            guard
                session.items.count == 1,                            // Accept only one drag item ...
                tableView.hasActiveDrag                              // ... from within this table view
            else {
                return UITableViewDropProposal(operation: .cancel)
            }
    
            return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
        }
    
        func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
            guard let destinationIndexPath = coordinator.destinationIndexPath else { return }
    
            for item in coordinator.items {
                if let sourceIndexPath = item.sourceIndexPath {
                    DispatchQueue.main.async {
                        tableView.moveRow(at: sourceIndexPath, to: destinationIndexPath)
                    }
                }
            }
        }
    }
    

    Clearly, if you were going to enable drag from this app to others, you would add UITableViewDragDelegate conformance here, and make your model objects conform to NSItemProviderReading and NSItemProviderWriting. But the above should be sufficient for dragging and dropping to reorder within a UITableView.

  • Related