天天看點

Swift 玩轉 3D Touch 之 Peek & Pop

apple_3dtouch.png

什麼是3D Touch

3D Touch 是iOS9之後專為 iPhone6s 機型加入的新特性,這一新技術移植于 Mac Book 上的 ForceTouch 更準确地說應該是 ForceTouch 在iPhone 上的實作吧。3D Touch 實質是一種新型的快捷單點觸控技術,在同一個點上通過不同的壓力感應觸發一種預覽行為。

在具體實作來說,3D Touch 包括以下三個技術内容:

  • Peek - 輕壓項目彈出預覽視窗
  • Pop - Peek 觸發之後再加力按壓預覽視窗彈出詳情視窗(相當于iOS 内的 Show Detail 行為)
  • Application shortcut - 當輕壓 App 圖示時彈出的快捷菜單項

當初在看WWDC之時覺得這是一個很Cool的技術,也是成為促使人們購買6s的一大理由,本以為在軟體實作上會比較麻煩,但在iOS9新的SDK中這一技術實作之簡單實在令我有點小興奮。在蘋果的開發者網站上雖然有3D Touch 的3D Touch 的示例代碼,是一個用

UITableView

實作的一個完整示例,但這個示例過于複雜大多數代碼都是用于處理

UITableView

的,這樣反而掩蓋了 3D Touch 實作的細節。特此,我重新寫了一個更加簡單的示例,旨在将展示如何一步步來實作 3D Touch 的功能。

Interface Builder 支援

與 Xcode 6 相比 Xcode 7 的可視化程式設計能力有了很大幅度的提高,至少有很多地方不需要再重重複複地去Hardcode那些無聊的界面代碼(這早該改進了,10幾年前的DELPHI早就做出了最好的可視化設計範本)。值得稱贊的是 3D Touch 在此可以不用寫一句代碼就能實作了!

方法極為簡單,建立一個工程,拖入一個新的 ViewController 到 IB 裡面,然後增加一個 segue 。然後在Segue的屬性中将 “Peek & Pop” 的勾選框勾上,那麼就實作 3D Touch了。是不是很簡單,很沒有技術含量?但我喜歡!因為有效率。

有圖有真相,看看下面這張圖我想隻要接觸了一點一點iOS程式設計的小夥伴們都一下就能搞出來了:

Interface Builder

如果向下深究你會發現,事情遠遠沒有這麼普通。因為通過IB我們還可以做更多的定制化。

!Storyboard Segue](

http://upload-images.jianshu.io/upload_images/80120-ceb8062b08e4d16b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

)

這裡有一些轉義 Peek 就是 Preview , Pop 就是Commit ,這一點我們得了解的。

從上圖就可知,當将選項從"Same as Commit Segue"/"Same as Action Segue" 改成 "Custom",那麼 我們可以将Preview 與 Commit 時所采用的視圖控制器指定成特定的控制器類型,以增加更多的可自定控制,具體做法就與綁定視圖到指定控制器一樣,設定一下類名就行了,在此就不多加贅述了。

程式設計實作 3D Touch

如果你覺得上面的方法還不能滿足你的控制需要,那麼我們還可以用Hardcode的方式來實作3D Touch。随然過程有點繁複,但對于了解3DTouch的本質卻是有着莫大的好處。

UIViewControllerPreviewingDelegate

我們隻要準備兩個視圖,一個為主視圖(

ViewController

)用于觸發 Peek,另一個為詳情視圖(

DetailViewController

)。

在iOS 中實作Peek 與 Pop 是很簡單的,iOS9的SDK中新增加了一個叫

UIViewControllerPreviewingDelegate

的接口。隻要實作這個接口就可以令我們的程式支援3D Touch 了。

import UIKit

class ViewController: UIViewController, UIViewControllerPreviewingDelegate {
        
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 注冊 3D Touch 的觸發視圖
        if traitCollection.forceTouchCapability == .Available {
            registerForPreviewingWithDelegate(self, sourceView: view)
        }
    }


    // Mark - 實作 UIViewControllerPreviewingDelegate

    func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {        
        return nil
    }
    
    func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) {
    }
}

``


3D Touch 的所有秘密都在這個 `UIViewControllerPreviewingDelegate` 接口之内了。這個接口隻有兩個方法,帶有傳回值的就是實作 Peek 也就是 Preview, 而沒有傳回值的就是 Pop 也就是Commit, 這一點很容易了解。但有點一必須指出,那就是如果要以程式設計方式實作3DTouch那就必須在控制器加載時就将 `UIViewControllerPreviewingDelegate` 通過 `registerForPreviewingWithDelegate` 方法注冊到目前的視圖控制器中,否則是不會觸發這個接口上的事件的。


### Peek (Preview)


```swift
func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {        
        return nil
}
           

如果這個方法傳回

nil

那麼

Peek

行為将會失敗,iPhone 上隻是會抖一下,而什麼都不會觸發的。

如果我們原來的項目中是通過表格顯示清單,點選表格單元進入詳情頁這種做法的話。這裡所傳回的 controller 執行個體就可以是原項目的詳情頁的controller 執行個體。隻是這個視圖不會具有 NavigationBar 和 Statusbar ,而隻是 view 的一個快照。當然,我們也可以單獨為項目的預浏行為編寫獨特的視圖控制器,在這裡直接傳回控制器的執行個體就是了。

在我前不久的文章中曾介紹過iOS9 中的

SFSafariViewController

,那麼在今天這個示例裡面我就偷個懶,模拟在浏覽器中的連結 Peek 行為,當使用者輕壓 示例中的 “Ray 的部落格” 字樣時就直接 Peek 一個 Safari 的浏覽器進行預覽。

那麼我就在上述的代碼中加入:

func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
        
    return  SFSafariViewController(URL: NSURL(string:"http://ray.dotnetage.com)!)
}
           

運作看效果,你會發現:無論我輕壓螢幕的哪一個位置都會彈出 Safari 浏覽器進行預覽,而不是像我所期望的,隻是在輕壓 "Ray 的部落格" 這個字樣時執行。這是因為當我們在前面采用了

registerForPreviewingWithDelegate(self, sourceView: view)

注冊3DTouch後,整個視圖的所有區域隻要被Peek 都直接執行 上面的代碼,如果我們要固定在特定的區域,那麼我們就得使用這裡的一個參數:

location:CGPoint

通過這個參數我們就可以推測 iOS 是通過檢測我們手按壓在螢幕的哪一個具體的點上而觸發 Peek 行為的。當然了我們指壓下的範圍一定是多個點,而這個location 隻是其中一個iOS認為最準确的位置而已。

要實作我的想法,隻要用

CGRectContainsPoint()

方法檢查一下目前的點是不是在目标區域内,如果不是傳回空就行了。那麼代碼就可以這樣寫:

func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
        
    return CGRectContainsPoint(peekButton.frame, location) ? SFSafariViewController(URL: NSURL(string:"http://ray.dotnetage.com")!) : nil
}
           

這就是最終效果:

預覽

如果我們壓下的是 table 的一個 cell 我們可以用

tableView.indexPathForRowAtPoint

方法重新擷取了對應的 cell 對象

動作菜單

當我們觸發了 Peek 行為之後,将預覽視圖向上推時可以在下方出現一個與 ActionSheet 類似的菜單快速操作目前預覽項目,這個功能就是

UIViewControllerPreviewingDelegate

的另一個方法

previewActionItems

。這個方法實作很簡單,隻是将項目的具體操作實作為一個

UIPreviewActionItem

數組并傳回即可,這個和 ActionSheet的實作方法極為相似。

注:

previewActionItems

是一個

UIViewController

的新增方法

以下代碼示例是生成一個帶有“删除”和“完成”的菜單項:

extension SFSafariViewController {
    
    override public func previewActionItems() -> [UIPreviewActionItem] {
        
        let deleteAction =  UIPreviewAction(title: "删除",
            style: UIPreviewActionStyle.Destructive,
            handler: {
                (previewAction,viewController) in
                
                NSLog("Delete")
                
        })
        
        let doneAction = UIPreviewAction(title: "完成",
            style: UIPreviewActionStyle.Default,
            handler: {
                (previewActin, viewController) in
                
                NSLog("Done")
        })
        
        return [doneAction, deleteAction]
    }
    
}

           

運作效果如下:

Peek

好了對于Peek的控制就是這麼多,但此時如果我們重壓觸發 Pop 會馬上什麼都沒有了!為什麼呢?因為我們還有另一個方法還沒有實作呢,這個方法實作起來也很簡單,直接用

showViewController()

方法将

viewControllerToCommit

這個參數給顯示出來就是了,從這裡我們就可以得出這樣的結論了,peek 被執行後所傳回的 控制器執行個體将會被傳遞到 這個方法的

commitViewController

中。

具體代碼實作如下:

func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) {
    showViewController(viewControllerToCommit, sender: self)
}
           

That's all! 将以上的代碼寫好 Load 到我們的手機上就可以體驗這個小示例了。有一點比較遺憾的是,模拟器是不能模拟 3D Touch 的,隻能在真機上調試,希望 Apple 會在下一個版本的 XCode 中加入3D Touch 的模拟器支援。

最後,奉上本文章的完整代碼示例:

https://github.com/DotNetAge/PeepPopExample

小結

關于 Application shortcut 是個比較蛋痛的功能,雖然它能支援靜态與動态菜單,但是寫起來内容也不少,是以今天還是打算先寫到這裡。以後再找時間專門寫一篇關于 Application shortcut 的文章吧。

繼續閱讀