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.
Quick links
- Program file overview
- Setup the datasource
- Check for 3D Touch support
- Implement the delegate
- Setup the peeking
- Setup the popping
- Adding preview actions
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")
]
}()
SwiftCheck 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)
}
}
SwiftImplement 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 {
...
}
SwiftThere 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 }
...
}
SwiftThe 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 }
SwiftIf 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
SwiftIf 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)
SwiftAnd 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
SwiftNow 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
}
SwiftSetup 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)
}
SwiftAnd 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.
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.
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]
}
SwiftWrapping 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.
14 Responses
Did you record the video in the simulator? If so, how did you get 3D touch to work in the simulator?
No, I used Screenflow (http://www.telestream.net/screenflow/overview.htm) to record the screen of my iPhone 6S.
I’d guessed it would be something like that! Sucks for those of us without the latest hardware 🙁
You should be able to buy the hardware now. Are you in a country where it is not for sale yet?
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.
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.
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.
Solid writeup. I got this working in just a few minutes after reading this. Good stuff.
thanks! glad you liked it.
This tutorial is incredibly simple and it just straight up works! Thanks a lot!
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.
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!)
Wow thanks man!
userinteraction for previewing view is disable what should i do to scroll in previewing view