3D touch peek and pop tutorial for your Swift application

3D Touch peek and pop tutorial

Apple added a touch-sensitive layer to the screen of the brand new iPhone 6s (plus).  With the coming of this new screen, they’ve added some new UI interactions like application shortcuts and peek and pop.

In this 3D touch peek and pop tutorial I will learn you how to implement this new way of interacting with your content by building a photo gallery.  When you press hard on the screen you’ll see a preview of the image and if you press really hard the preview will pop into a detail view.

At the end of this tutorial I’ll show you how to add preview actions. This way you can interact with the content without going to the detail view.  You can do this by swiping up while you are previewing the content.

https://www.youtube.com/watch?v=s64iCMIsM5Y&feature=youtu.be

Quick links

Program file overview

For this application we’ll have the following classes:

  • PhotoCollectionViewController: this is the entry point for the application. We’ll show a list of photos in a UICollectionViewController.
  • PhotoCollectionViewCell: a subclass of UICollectionViewCell which will just hold an IBOutlet.
  • Photo: this struct will serve as our data object in which we’ll store some data like the name of the image file, the city where the picture was taken and a caption.
  • DetailViewController: when a user taps on a photo, we will transition to this view controller and show the user the photo and a caption.

Setup the datasource

Our UICollectionViewController needs to be fed with data. To do this we’ll create an array of Photo instances.  I’ll lazy load this datasource, so the array will only be created when the datasource is first accessed.

lazy var photos:[Photo] = {
    
    return [
        Photo(caption: "Lovely piece of art in Bordeaux", imageName: "bordeaux", city: "Bordeaux"),
        Photo(caption: "Cosy lake beach in France", imageName: "lake", city: "Bordeaux"),
        Photo(caption: "Harbour in France", imageName: "harbour", city: "Rouffiac"),
        Photo(caption: "Buda beach in Kortrijk", imageName: "buda", city: "Kortrijk")
    ]
    
}()
Swift

Check for 3D Touch support

If we want to integrate peek and pop, we need to be sure that the device has a 3D Touch screen. You can check this by asking the traitcollection object for the value of the forceTouchCapability property.

If the capability is available, we can register our view controller for peek and pop by calling registerForPreviewingWithDelegate:sourceView:I’ll do this check in viewDidLoad.

override func viewDidLoad() {
    
    super.viewDidLoad()
    
    if( traitCollection.forceTouchCapability == .Available){
        
        registerForPreviewingWithDelegate(self, sourceView: view)
        
    }
    
}
Swift

Implement the delegate

We just agreed to be the delegate for peeking and popping, so we need to implement the delegate methods.  These 2 methods are declared in UIViewControllerPreviewDelegate, before we can implement these methods we need to implement this protocol.  Just add the name of the protocol after the superclass.

class PhotoCollectionViewController: UICollectionViewController, UIViewControllerPreviewingDelegate {

...

}
Swift

There are 2 methods that need to be implemented, the first method will setup a view controller for peeking, the other method is for popping.

Setup the peeking

We need to implement the method previewContext:viewControllerForLocation:, this method will return a view controller object which in our case will be an instance of DetailViewController.

First we need to find out which photo the user touched, so we can find the correct model in our datasource.  We can ask the collectionview for the index path of the item in the collection view at a certain location. The location is passed as an argument by the delegate method.  If we can’t find an indexPath, we bail out.

With this indexPath, we can now check if there is a cell at that indexPath in the collectionView. If not we bail out.

func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
        
        guard let indexPath = collectionView?.indexPathForItemAtPoint(location),
              let cell = collectionView?.cellForItemAtIndexPath(indexPath)  
        else { return nil }
...

}
Swift

The next step is to create an instance of the DetailViewController class.  I’ve added a Storyboard ID to the DetailViewController scene in my storyboard. If this view controller fails to be created, we bail out.

guard let detailVC = storyboard?.instantiateViewControllerWithIdentifier("DetailViewController") as? DetailViewController else { return nil }
Swift

If all the above steps worked, we can fetch the data object from the datasource and pass this object to the DetailViewController so it can set up it’s imageview and label.

let photo = photos[indexPath.row]
detailVC.photo = photo
Swift

If you check the code in DetailViewController, you’ll see that in the viewDidLoad method there is a check to see if the photo property is available. If so, the image, caption and title are set.

We only need to do 2 more step, one is to set the preferredContentSize on the DetailViewController instance.

detailVC.preferredContentSize = CGSize(width: 0.0, height: 300)
Swift

And the last step is to set the sourceRect property. This property accepts a CGRect and will blur everything outside this rect, and keep the content inside the CGRect sharp.  The rect is the same frame as the cell of our collectionview.

previewingContext.sourceRect = cell.frame
Swift

Now we just have to return the instance of DetailViewController.  So this is the final method.

func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
    
    guard let indexPath = collectionView?.indexPathForItemAtPoint(location) else { return nil }
    
    guard let cell = collectionView?.cellForItemAtIndexPath(indexPath) else { return nil }
    
    guard let detailVC = storyboard?.instantiateViewControllerWithIdentifier("DetailViewController") as? DetailViewController else { return nil }
    
    let photo = photos[indexPath.row]
    detailVC.photo = photo
    
    detailVC.preferredContentSize = CGSize(width: 0.0, height: 300)
    
    previewingContext.sourceRect = cell.frame
    
    return detailVC
}
Swift

Setup the popping

Popping is easy, we’ll just use the same view controller we just created.  Just implement the delegate method and call the showViewController:sender: method.

func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) {
    
    showViewController(viewControllerToCommit, sender: self)
    
}
Swift

And you’re done! Run the application and press on a picture, it should popup and if you press harder you’ll pop inside the DetailViewController.

3D Touch Peek
3D Touch Peek

Adding preview actions

If you want to allow the user to swipe up when they are in the peek phase and do an action, you can implement something called preview actions.

3D Touch preview actions
3D Touch preview actions

The implementation is really simple. In the DetailViewController you need to implement the previewActionItems method.  This method returns an array of objects that implement the UIPreviewActionItem protocol.

A UIPreviewAction (which implements this protocol) has 3 arguments you have to provide

  • title: the title for the button
  • style: the style for the button
  • handler: a closure which gets executed when the user taps the action

For this application I’ll add 2 dummy buttons, one to like the photo and one to delete the photo.

Add the following code to your DetailViewController class.

override func previewActionItems() -> [UIPreviewActionItem] {
    
    let likeAction = UIPreviewAction(title: "Like", style: .Default) { (action, viewController) -> Void in
        print("You liked the photo")
    }
    
    let deleteAction = UIPreviewAction(title: "Delete", style: .Destructive) { (action, viewController) -> Void in
        print("You deleted the photo")
    }
    
    return [likeAction, deleteAction]
    
}
Swift

Wrapping up

That’s it! You succesfully implemented 3D touch peek and pop functionality.
I hope you liked this tutorial, if you have any questions … just leave a comment.

Sourcecode

The code for this project is available on GitHub.

Share this post

14 Responses

  1. Did you record the video in the simulator? If so, how did you get 3D touch to work in the simulator?

      1. I’d guessed it would be something like that! Sucks for those of us without the latest hardware 🙁

          1. The hardware is available, but I can’t justify buying a new device! I’ve found a workaround to test 3D Touch though so I’m able to test it anyway.

          2. Yeah it is quite a pain to put 700-800 euros on the table every year. But I sell my previous phone every year for 400-500 euro’s, so that makes it doable 🙂 But it’s still a big cost every year nonetheless.

    1. You can just use QuickTime Player to record your iPhone’s screen. Plug your iPhone into your Mac through USB, open QuickTime and click on the arrow next to the record button. Select your iPhone as the video recording hardware.

  2. I appreciate the tutorial. I just hate the plugin you used to post code snippets. It keeps hiding the first line of code whenever I try to view more or copy and paste.

  3. By the way, I had a weird bug in which the selection rect wasn’t working properly. Basically, if I force-touched the center of my images, peek and pop didn’t work. Also, the more I scrolled down, the weirder it behaved.

    The solution was to change this:

    registerForPreviewingWithDelegate(self, sourceView: view)

    With this:

    registerForPreviewingWithDelegate(self, sourceView: self.collectionView!)

  4. userinteraction for previewing view is disable what should i do to scroll in previewing view

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts