Одна из моих любимых функций на iPhone 6s — это новые 3D Touch Peek and Pop . Peek and Pop полагается на чувствительность к давлению, чтобы предложить пользователю всплывающее окно предварительного просмотра с помощью нажатия (peek) или позволяет им перемещаться по этому элементу более глубоким нажатием (pop). И то, и другое дает неожиданный тактильный щелчок, а просмотр может также отображать набор контекстно-зависимых действий предварительного просмотра.
Так получилось, что в течение последней недели я обновлял свой браузер фотографий на основе PHImageManager . Это проект, который я начал еще в январе, и я хотел обновить его до Swift 2, чтобы включить в следующую версию Nodality . Хотя Nodality — это приложение для iPad, этот компонент универсален и является идеальным кандидатом для моего первого знакомства с Peek и Pop.
Интерактивный дизайн
Для устройств, не поддерживающих 3D Touch, интерактивный дизайн моего фото-браузера довольно прост: верхний сегментный элемент управления позволяет пользователю перемещаться между своими коллекциями и, касаясь изображения, выбирает его и возвращает элемент управления в главное приложение. При длительном нажатии пользователь может переключать статус избранного элемента с помощью UIAlertController.
Для устройств 3D Touch пользователь может щелкнуть изображение, чтобы открыть предварительный просмотр, а затем переключить статус избранного с помощью действия UIPreviewAction. Более глубокий щелчок выбирает изображение и возвращает управление хост-приложению.
Настройка
Я не хочу терять функциональность при длительном нажатии для устройств без 3D Touch, поэтому во время инициализации я смотрю на traitCollection, чтобы либо зарегистрировать браузер фотографий для предварительного просмотра, либо внедрить средство распознавания жестов при длинном нажатии. Поскольку сам компонент представлен модально, его traitCollection утверждает, что он не был принудительно включен, поэтому я смотрю на raitCollection ключевого окна приложения:
if UIApplication.sharedApplication().keyWindow?.traitCollection.forceTouchCapability == UIForceTouchCapability.Available
{
registerForPreviewingWithDelegate(self, sourceView: view)
}
else
{
let longPress = UILongPressGestureRecognizer(target: self, action: "longPressHandler:")
collectionViewWidget.addGestureRecognizer(longPress)
}
Заглянув
Чтобы зарегистрировать браузер фотографий для предварительного просмотра с делегатом, он должен реализовать UIViewControllerPreviewingDelegate, а для просмотра — вызывать previewingContext (viewControllerForLocation). Здесь мне просто нужно вернуть экземпляр контроллера представления, который я хочу использовать в качестве предварительного просмотра. Когда пользователь коснулся изображения, я создал экземпляр кортежа с именем touchedCel типа (UICollectionViewCell, NSIndexPath), который ссылается на изображение, к которому прикоснулись, и с его помощью я могу получить PHAsset, необходимый для предварительного просмотра, и передать его моему PeekViewController:
func previewingContext(previewingContext: UIViewControllerPreviewing,
viewControllerForLocation location: CGPoint) -> UIViewController?
{
guard let touchedCell = touchedCell,
asset = assets[touchedCell.indexPath.row] as? PHAsset else
{
return nil
}
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
}
Контроллер предварительного просмотра, PeekViewController, повторно использует ImageItemRenderer — тот же инструмент визуализации элементов, что и мой основной UICollectionView, поэтому весь код для запроса уменьшенного изображения уже был доступен:
class PeekViewController: UIViewController
{
let itemRenderer: ImageItemRenderer
required init(frame: CGRect)
{
itemRenderer = ImageItemRenderer(frame: frame)
super.init(nibName: nil, bundle: nil)
preferredContentSize = frame.size
view.addSubview(itemRenderer)
}
var asset: PHAsset?
{
didSet
{
if let 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()
{
if let 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)
{
guard let touchedCell = touchedCell,
asset = assets[touchedCell.indexPath.row] as? PHAsset else
{
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!