NEW! Master Swift design patterns with my latest book! >>

How to add peek and pop to a UITableView

Paul Hudson       @twostraws

Peek and pop are features of 3D Touch that let users press hard on something to show more information. It’s surprisingly easy to add when working with table view cells and collection view cells, and downright trivial if you use storyboards and segues.

First, the trivial case: if you’re using storyboards and segues, Xcode can do all the work for you. If you want to see how easy it is, create a new iOS app using the Master-Detail project template, then open Main.storyboard. Find the Show Detail segue that moves from the table view to the detail view, then check the box marked “Preview & Commit Segues” in the attributes inspector.

That’s it: iOS will automatically make peek and pop code for that segue – you can press hard on a table view cell to bring up the detail controller as a preview, then press harder to make it full screen. (Note: if you’re using the simulator this isn’t easy to do – try using a real device!)

If you’re not using segues you need to write some code yourself. First, make your view controller conform to UIViewControllerPreviewingDelegate so that you’re able to respond to previewing requests correctly. Second, you need to tell the system we want to support previewing by calling registerForPreviewing() in your viewDidLoad() method:

registerForPreviewing(with: self, sourceView: tableView)

That tells iOS that the view controller is able to respond to previewing requests for your table view.

There are two methods you need to fill in for the UIViewControllerPreviewingDelegate protocol: which view controller should be shown when the user presses at a certain location, and when the user presses harder how do you want to present it.

For many apps the code to create a previewing detail controller will be the same code to create a regular detail view controller, so it’s a good idea to create a method to instantiate a detail view controller and configure it as needed.

For example, you might have something like this:

func detailViewController(for index: Int) -> DetailViewController {
    guard let vc = storyboard?.instantiateViewController(withIdentifier: "Detail") as? DetailViewController else {
        fatalError("Couldn't load detail view controller")
    }

    vc.selectedItem = index
    return vc
}

You would then have something like this inside your didSelectRowAt method:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let vc = detailViewController(for: indexPath.row)
    navigationController?.pushViewController(vc, animated: true)
}

Now for the important part: filling in the peek and pop methods.

You need to write a viewControllerForLocation method, which gets called with whatever location on the screen the user touched. You can pass that straight to the table view to ask it what row is at that location, then tell the previewing context to use that row as its source rect – iOS will cause that row to zoom up while the others get blurred. Finally you can return the correct view controller, but if no cell was tapped you need to return nil instead so that no 3D Touch effect happens.

Here’s how that looks in code:

func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
    if let indexPath = tableView.indexPathForRow(at: location) {
        previewingContext.sourceRect = tableView.rectForRow(at: indexPath)
        return detailViewController(for: indexPath.row)
    }

    return nil
}

As for the pop effect, that’s just one line of code inside a commit method. You’ll be given the view controller that’s currently being peeked, so your method just needs to decide how to present it. For example, if you’re using a navigation controller you probably want to call pushViewController() here – iOS will automatically convert that into a pop zoom animation with bounce effect, but the end result will be a pushed view controller so you can go back like normal.

Here’s that in Swift:

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

That’s all the code – it’s not really that hard to do, and UIKit makes the result look great.

Available from iOS 9.0

Did this solution work for you? Please pass it on!

Other people are reading…

About the Swift Knowledge Base

This is part of the Swift Knowledge Base, a free, searchable collection of solutions for common iOS questions.

Help support Hacking with Swift

This site is funded by Hacking with Swift supporters who buy my e-books. If you can, please support my work – it comes packed with bonus material!

MASTER SWIFT NOW
Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Practical iOS 11 Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let me know!

Click here to visit the Hacking with Swift store >>