One of my favourite features on the iPhone 6s is the new 3D Touch Peek and Pop. Peek and Pop relies on pressure sensitivity to offer the user a transient preview pop up with a press (peek) or allows them to navigate that item with a deeper press (pop). Both give a satisfying haptic click and peeking can also display a set of context sensitive preview actions.
It so happens that over the last week or so, I've been updating my PHImageManager based photo browser. This is a project I started back in January and I wanted to update to Swift 2 to include in the next version of Nodality. Although Nodality is an iPad application, the component is universal and is the perfect candidate for my first foray into Peek and Pop.
Interaction Design
For non 3D Touch devices, my photo browser's interaction design is pretty simple, the top segmented control allows the user to navigate between their collections and touching an image selects it and returns control back the the host application. With a long press, the user can toggle the favourite status of an item via a UIAlertController.For 3D Touch devices, the user can click on an image to pop up a peek preview and then toggle the favourite status via a UIPreviewAction. A deeper pop selects an image and returns control to the host application.
Setting Up
I don't want to lose the long press functionality for non 3D Touch devices, so during initialisation, I look at the traitCollection to either register the photo browser for peek previewing or implement a long press gesture recogniser. Because the component itself is modally presented, its traitCollection claims not to be force touch enabled, so I look at the traitCollection of the application's key window: ifUIApplication.sharedApplication().keyWindow?.traitCollection.forceTouchCapability == UIForceTouchCapability.Available
{
registerForPreviewingWithDelegate(self, sourceView: view)
}
else
{
let longPress = UILongPressGestureRecognizer(target: self, action: "longPressHandler:")
collectionViewWidget.addGestureRecognizer(longPress)
}
Peeking
To register the photo browser for previewing with delegate, it must implement UIViewControllerPreviewingDelegate and for peeking, previewingContext(viewControllerForLocation) is called. Here I simply need to return an instance of the view controller I want to act as the preview. When the user touched an image, I instantiated a tuple named touchedCell of type (UICollectionViewCell, NSIndexPath) that refers to the touched image and with that I can get the PHAsset required for previewing and hand it to my PeekViewController: func previewingContext(previewingContext: UIViewControllerPreviewing,
viewControllerForLocation location: CGPoint) -> UIViewController?
{
guardlet touchedCell = touchedCell,
asset = assets[touchedCell.indexPath.row] as? PHAssetelse
{
returnnil
}
let previewSize = min(view.frame.width, view.frame.height) * 0.8
let peekController = PeekViewController(frame: CGRect(x: 0, y: 0,
width: previewSize,
height: previewSize))
peekController.asset = asset
return peekController
}
The previewing view controller, PeekViewController, reuses ImageItemRenderer - the same item renderer as my main UICollectionView, so all the code for requesting a thumbnail sized image was already available:
class PeekViewController: UIViewController
{
let itemRenderer: ImageItemRenderer
requiredinit(frame: CGRect)
{
itemRenderer = ImageItemRenderer(frame: frame)
super.init(nibName: nil, bundle: nil)
preferredContentSize = frame.size
view.addSubview(itemRenderer)
}
var asset: PHAsset?
{
didSet
{
iflet asset = asset
{
itemRenderer.asset = asset;
}
}
}
}
Adding the preview action to toggle the favourite status of the asset it a simple as returning an array of UIPreviewActionItem. PeekViewController already knows what asset needs to be toggled and the shared photo library is a singleton, so the code is just:
var previewActions: [UIPreviewActionItem]
{
return [UIPreviewAction(title: asset!.favorite ? "Remove Favourite" : "Make Favourite",
style: UIPreviewActionStyle.Default,
handler:
{
(previewAction, viewController) in (viewController as? PeekViewController)?.toggleFavourite()
})]
}
func toggleFavourite()
{
iflet targetEntity = asset
{
PHPhotoLibrary.sharedPhotoLibrary().performChanges(
{
let changeRequest = PHAssetChangeRequest(forAsset: targetEntity)
changeRequest.favorite = !targetEntity.favorite
},
completionHandler: nil)
}
}
The final result, with very little code, is a fully functioning "peek" with the nice system animation and that satisfying haptic click:
Popping
Popping is easier still. Here, I implement the previewingContext(commitViewController) method of UIViewControllerPreviewingDelegate. My photo browser has a method named requestImageForAsset() which requests the image for an asset and then dismisses the browser, so I simply invoke that: func previewingContext(previewingContext: UIViewControllerPreviewing,
commitViewController viewControllerToCommit: UIViewController)
{
guardlet touchedCell = touchedCell,
asset = assets[touchedCell.indexPath.row] as? PHAssetelse
{
dismissViewControllerAnimated(true, completion: nil)
return
}
requestImageForAsset(asset)
}
Conclusion
I've only had my phone for a matter of hours and already I find peeking and popping a very natural way of interacting with it. Considering the simplicity with which peek and pop can be implemented, I'd humbly suggest that adding it to your own applications is a pretty big win!As always, the source code for this project is available at my GitHub repository here. Enjoy!