How to implement Peek & Pop Feature of specific row of a table manually via code?

1k Views Asked by At

Goal

I'm trying to add the peek and pop feature to my table row


enter image description here

2

There are 2 best solutions below

2
On BEST ANSWER

You are not getting the cell from the tableview. So the steps are as follows:

  1. Get the indexPath using the location point from UIViewControllerPreviewingDelegate method.
  2. Use the indexPath to get cell from the tableView.
  3. After getting the cell convert the cell frame to tableview frame.
  4. Give the frame as sourceRect to previewingContext.

Code Below:

In viewDidLoad

if self.traitCollection.forceTouchCapability == .available {
    registerForPreviewing(with: self, sourceView: tableView)
}

In Extension:

extension ProfileViewController: UIViewControllerPreviewingDelegate {

    func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
        if let indexPath = tableView.indexPathForRow(at: location), let cell = tableView.cellForRow(at: indexPath) {
            previewingContext.sourceRect = tableView.convert(cell.frame, to: self.tableView)
            guard let detailViewController = storyboard?.instantiateViewController(withIdentifier: "profileDetail") as? ProfileDetailViewController else {
                return nil
            }
            return detailViewController
        }
        return nil
    }

    func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
        self.navigationController?.pushViewController(viewControllerToCommit, animated: true)
    }
}
0
On

Usually, I am doing that using a simple cell extension:

class ForceTouchCell: UITableViewCell {
    var previewingContext: UIViewControllerPreviewing?

    func registerPreview(on controller: UIViewController, with delegate: UIViewControllerPreviewingDelegate) {
        self.previewingContext = controller.registerForPreviewing(with: delegate, sourceView: self)
    }

    func unregisterPreview(on controller: UIViewController) {
        guard let previewingContext = previewingContext else {
            return
        }

        controller.unregisterForPreviewing(withContext: previewingContext)
        self.previewingContext = nil
    }
}

Then on the controller:

class MyController: UIViewController, UITableViewDelegate {
    var tableView: UITableView!

    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        guard traitCollection.forceTouchCapability != previousTraitCollection?.forceTouchCapability else {
            return
        }

        let visibleCells = tableView.visibleCells
               .compactMap { $0 as? ForceTouchCell }

        if traitCollection.forceTouchCapability == .available {
           visibleCells.forEach {
               $0.registerPreview(on: self, with: self)
           }
        } else {
           visibleCells.forEach {
               $0.unregisterPreview(on: self)
           }
        }
    }

    func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if let forceTouchCell = cell as? ForceTouchCell {
            if self.traitCollection.forceTouchCapability == .available {
                forceTouchCell.registerPreview(on: self, with: self)
            }
         }
    }

    func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
        if let forceTouchCell = cell as? ForceTouchCell {
            forceTouchCell.unregisterPreview(on: self)
        }
    }
}

extension MyConroller: UIViewControllerPreviewingDelegate {
    func previewingContext(
    _ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint
) -> UIViewController? {
    guard
        let cell = (previewingContext.sourceView as? ForceTouchCell),
        let indexPath = tableView.indexPath(for: cell)
    else {
        return nil
    }

    // use the indexPath/cell to create preview controller
}

The nice thing about this solution is the fact that it can be easily shared across controllers, e.g. using protocols with default implementations.