我们回首 iPhone 的历程,不禁感叹它是如何不断改变我们对手机的认知的。从触屏改变手机的定义开始,距离传感器、光线传感器,到三轴陀螺仪、GPS、运动传感器、再到指纹。这些功能一步步地拓展 iPhone 的能力,不断地改变着我们的生活。
iPhone 6s 再次增加了新的功能 -- 3D Touch。 它为iOS 设备的操作增加了另一个维度的能力,为用户提供了另一个操作体验,甚至是改变了用户的操作习惯。随着设备的普及和软件的跟进,3D Touch 也许会像指纹解锁一样,不可或缺的存在。
关于 3D Touch, Apple 主要为我们提供了两种封装好的功能:
Home screen quick action 的表现形式非常简单,利用 3D Touch 按主屏幕图标,则会弹出该App 的快捷菜单。点击快捷菜单则会唤起app,并进入到相对应的功能。
主屏幕快捷菜单分为 静态 和 动态 的两种。静态即固定的菜单。动态则代表菜单项是可变的。例如激活一款聊天软件的 Quick Action,出现三个你联系最频繁的人。这便需要动态菜单来实现。
当然,他们的开发也同表现形式一样简单。
创建一个静态的快捷菜单,只需要简单地在 Info.plist
中添加一个 UIApplicationShortcutItems
的 Array 即可。
你可以为快捷菜单指定标题、系统或自定义的icon等,所有这些,都只需要在 UIApplicationShortcutItems
下添加一些key 和 value 即可。这些key可以在 这里 找到。
iOS 10 中,快捷菜单同时可以附带显示一个 Widget ,也就是以前的 App Extension。如果你提供了多个 Extension,可以在 Info.plist
中添加 UIApplicationShortcutWidget
,其 Value 代表快捷菜单中展示的 Extension 的 Bundle id.
创建一个动态的快捷菜单也非常容易,只需要初始化并配置 UIApplicationShortcutItem
, UIMutableApplicationShortcutItem
和 UIApplicationShortcutIcon
这三个类,并将其添加到 AppDelegate 中的 shortcutItems
属性即可。
值得一提的是,静态快捷菜单在 App 被安装的那一刻就可用了。区别于静态菜单,动态快捷菜单只有当第一次启动后才可用。
这里分 当 App 被 kill 和 当 App 未被 kill 两种情况。
当 App 仍然在运行的情况下,点击快捷菜单会触发以下方法:
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) { // 做你想做的事 // 最后不要忘了调用completionHandler() }
当 App 被 kill 的情况下,点击快捷菜单会触发以下方法:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // 通过 UIApplicationLaunchOptionsShortcutItemKey 在 launchOptions 中取得 UIApplicationShortcutItem // 然后做你想做的事 }
macOS 中按下空格键即可预览各种各样的文件。通过 3D Touch 现在它也来到了 iOS 中。这一操作被 Apple 很形象地称为 Peek and Pop 。
Peek and Pop 将传统的 Push 操作分为了两步,当你的手指按压某行列表,背景开始模式模糊,然后出现一个预览界面,然后继续增加压力,伴随着俏皮的弹性动画,下一个界面呈现在你眼前。在 API 中,这两部分别被称为 Preview
和 Commit
。
为 App 添加 Peek and Pop 非常简单,只要遵循以下几步:
UIViewControllerPreviewingDelegate
协议 registerForPreviewing(with:sourceView:)
注册该ViewController show(_:sender:)
即可。 编码的过程中,要注意检查 3D Touch 的可用性。 因为这项功能同定位一样,用户是可以将其关闭的!为了保证所有的用户都能使用到你 App 的功能,应当依据 3D Touch 的可用性,来编写不同的代码。 当 3D Touch 可用时,那就用上着炫酷的新功能吧!如果不支持 3D Touch,我们还有另外一个备选方案 -- UILongPressGestureRecognizer
.
另外,不要在 UIViewControllerPreviewingDelegate 中做非常耗时的工作,那会造成界面卡顿。
合理地利用 Peek and Pop 可以为用户带来无缝的体验。 例如,下一个界面会有大量的初始化工作,在preview的过程中,你可以预先加载并提供部分预览(同时也为下一个界面做准备),记住不要卡住主线程,等到 Pop 时,界面已经初始化好了。这为用户提供了一个非常好的体验。
在 Preview 的过程中,用户可以上滑来唤出类似 Action Sheet 的菜单。实现这一功能只需要重写 ViewController 中的 previewActionItems() -> [UIPreviewActionItem]
方法即可。 系统提供了和 UIAlertAction
非常类似的 UIPreviewAction
,来实现 UIPreviewActionItem
。
与 Action Sheet 不同的是,系统提供了 UIPreviewActionGroup
类,实现子菜单的功能。
在开发 Peek and Pop 的过程中,请记住以下原则
iOS 10 中为我们带来了全新的 API,可以让你自定义 Peek and Pop 操作。 我们只需要让某个对象遵循 UIPreviewInteractionDelegate
协议,并在相对于的代理方法中做我们想做的事情就可以了。
UIPreviewInteractionDelegate 一共包含以下四个方法:
// 必须实现 // 这个方法可以让你精细地控制从按压到 Preview 触发过程中发生的事 // 配合 UIViewControllerTransitioningDelegate ,可以做类似新郎微博的 + 号菜单效果。 @available(iOS 10.0, *) public func previewInteraction(_ previewInteraction: UIPreviewInteraction, didUpdatePreviewTransition transitionProgress: CGFloat, ended: Bool) // transitionProgress ranges from 0 to 1
// 这个方法也是必须实现的,用来处理一些 Interaction 被打断情况 // 比如说:接到一个电话。 @available(iOS 10.0, *) public func previewInteractionDidCancel(_ previewInteraction: UIPreviewInteraction)
// 你可以用这个方法来控制 Delegate 的触发。 // return false 则不会触发其他代理方法 @available(iOS 10.0, *) optional public func previewInteractionShouldBegin(_ previewInteraction: UIPreviewInteraction) -> Bool
// 用这个方法来精细地控制 Preview -> Commit 阶段 @available(iOS 10.0, *) optional public func previewInteraction(_ previewInteraction: UIPreviewInteraction, didUpdateCommitTransition transitionProgress: CGFloat, ended: Bool)
UIPreviewInteractionDelegate
让 3D Touch 变得更灵活了。如果我们希望 iOS 9 也支持这样的功能怎么办呢? 不用担心,Apple 还提供了更底层的 API。
在 UITouch 中有两个关键的属性为我们提供了压力相关数据的存储。
@property(nonatomic,readonly) CGFloat force NS_AVAILABLE_IOS(9_0); @property(nonatomic,readonly) CGFloat maximumPossibleForce NS_AVAILABLE_IOS(9_0);
force
代表当前 Touch 的压力系数,默认是 0.0 ~ 1.0。你也可以通过 maximumPossibleForce
来调整 force
的上限,来为其提供更宽的变化范围。
上面的 API 仅在支持 3D Touch 和 Apple Pencil 的设备上可用,所以在使用前,不要忘了检查 UITraitCollection
中的 forceTouchCapability
,来确定以上 API 是否可用。
为什么将 forceTouchCapability 放在 UITraitCollection 中?
因为 Size Classes 将设备抽象成了 Size.
WWDC 2016 Sssion 228 - A Peek at 3D Touch
WWDC 2016 Sssion 228 - A Peek at 3D Touch Presentation Slides
Adopting 3D Touch
Apple WWDC 示例代码: AppChat