3

Goal

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


enter image description here

Community
  • 1
  • 1
code-8
  • 54,650
  • 106
  • 352
  • 604
  • 1
    Have you set a breakpoint in the `previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint)` method? Is it called? Does it return a view controller? What device are you testing on? Is the collection view controller on your current VC or the detail vc? – Paulw11 Oct 18 '18 at 19:25
  • This looks like a good solution: https://stackoverflow.com/a/46648566/341994 – matt Oct 18 '18 at 19:33
  • It seems that the error you posted is unrelated to your problem. – onnoweb Oct 22 '18 at 21:09
  • -1, even Apple has a proper sample app to show the implementation for peek'n'pop: https://developer.apple.com/documentation/uikit/peek_and_pop/implementing_peek_and_pop – holex Oct 29 '18 at 13:48

2 Answers2

2

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)
    }
}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Priyam Dutta
  • 702
  • 5
  • 19
  • If you are not using `detailViewController` for anything other than to return it, you can get rid of the `guard` and simply do `return storyboard?.instantiateViewController(withIdentifier: "profileDetail") as? ProfileDetailViewController`, which will be equivalent to your current code, but shorter and simpler – Dávid Pásztor Oct 29 '18 at 13:47
  • The problem with this solution is that you are not registering the cells themselves, therefore you won't get that animated border around them when they are pressed. – Sulthan Oct 29 '18 at 14:01
1

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.

Sulthan
  • 128,090
  • 22
  • 218
  • 270