感謝ShengPan的投稿
我們回首 iPhone 的歷程,不禁感歎它是如何不斷改變我們對手機的認知的。從觸屏改變手機的定義開始,距離傳感器、光線傳感器,到三軸陀螺儀、GPS、運動傳感器、再到指紋。這些功能一步步地拓展 iPhone 的能力,不斷地改變著我們的生活。
iPhone 6s 再次增加了新的功能 -- 3D Touch。 它為iOS 設備的操作增加了另一個維度的能力,為用戶提供了另一個操作體驗,甚至是改變了用戶的操作習慣。隨著設備的普及和軟件的跟進,3D Touch 也許會像指紋解鎖一樣,不可或缺的存在。
關於 3D Touch, Apple 主要為我們提供了兩種封裝好的功能:
主屏幕快捷菜單 ( Home screen quick action)
預覽 ( peek and pop )
Home screen quick action
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 // 然後做你想做的事 }
Peek and Pop
macOS 中按下空格鍵即可預覽各種各樣的文件。通過 3D Touch 現在它也來到了 iOS 中。這一操作被 Apple 很形象地稱為Peek and Pop。
Peek and Pop 將傳統的 Push 操作分為了兩步,當你的手指按壓某行列表,背景開始模式模糊,然後出現一個預覽界面,然後繼續增加壓力,伴隨著俏皮的彈性動畫,下一個界面呈現在你眼前。在 API 中,這兩部分別被稱為 Preview 和 Commit。
為你的 App 添加 Peek and Pop
為 App 添加 Peek and Pop 非常簡單,只要遵循以下幾步:
讓需要預覽的 ViewController 遵循 UIViewControllerPreviewingDelegate 協議
調用 registerForPreviewing(with:sourceView:) 注冊該ViewController
在 preview 代理方法中提供一個預覽的ViewController,並設置好 context 的 sourceRect.
在 commit 代理方法中,直接調用 show(_:sender:) 即可。
編碼的過程中,要注意檢查 3D Touch 的可用性。 因為這項功能同定位一樣,用戶是可以將其關閉的!為了保證所有的用戶都能使用到你 App 的功能,應當依據 3D Touch 的可用性,來編寫不同的代碼。 當 3D Touch 可用時,那就用上著炫酷的新功能吧!如果不支持 3D Touch,我們還有另外一個備選方案 -- UILongPressGestureRecognizer。
另外,不要在 UIViewControllerPreviewingDelegate 中做非常耗時的工作,那會造成界面卡頓。
合理地利用 Peek and Pop 可以為用戶帶來無縫的體驗。 例如,下一個界面會有大量的初始化工作,在preview的過程中,你可以預先加載並提供部分預覽(同時也為下一個界面做准備),記住不要卡住主線程,等到 Pop 時,界面已經初始化好了。這為用戶提供了一個非常好的體驗。
Preview Action
在 Preview 的過程中,用戶可以上滑來喚出類似 Action Sheet 的菜單。實現這一功能只需要重寫 ViewController 中的 previewActionItems() -> [UIPreviewActionItem]方法即可。 系統提供了和 UIAlertAction 非常類似的 UIPreviewAction,來實現 UIPreviewActionItem。
與 Action Sheet 不同的是,系統提供了 UIPreviewActionGroup 類,實現子菜單的功能。
在開發 Peek and Pop 的過程中,請記住以下原則:
讓合適的內容支持 Peek and Pop (不要濫用這項特性)
始終返回相同的預覽界面 (保持一致性和可預測性)
不要在preview代理方法中花太多的時間
為 context 設置正確的 sourceRect
UIPreviewInteractionDelegate
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。
Low Level Force 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。
3D Touch 的最佳實踐
每個 app 都應該提供 quick actions。其簡創建簡單,卻能帶來巨大的價值。
為高價值的任務提供一個快速的入口。
很重要的一點,你需要保證 quik action 的可預測性,用戶們如果經常使用它,卻發現每一次的結果都不一樣,就會因此而困惑。
即使你做了 3D touch 用戶也可以禁用這一項功能,因此不能過度依賴這項特性來做一些必須的事情,同時也要准備好備選方案。
總結
主頁的 quick action 可以讓你的app 直接進入相關行為。
Peek and Pop 允許你快速預覽內容,並導航到對應界面。
UIPreviewInteraction 讓你可以更精細地控制 3D Touch。
用戶們期望你支持 3D touch。
參考資料
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