Swift 程式語言

3D Touch及Quick Actions簡介:建構一個數位電子秤App

3D Touch及Quick Actions簡介:建構一個數位電子秤App
3D Touch及Quick Actions簡介:建構一個數位電子秤App
In: Swift 程式語言

你會否好奇,為什麼Force Touch 在iPhone上面突然改名叫3D Touch?不用奇怪,你也不是第一個提出疑問的人。不久之前Craig Federighi(註:蘋果公司軟體工程高階副總裁,主管iOS軟體及Mac軟體),很顯然的也搞不清楚這個這個技術應有的名子,所以在產品發表會的簡報上面,介紹這個新技術的時候提到的3D Touch,但這名詞卻從此一鳴驚人。其實叫做Force Touch 本身也沒有什麼問題啦?只我們已經有太多星際大戰相關的笑話了不是嗎?

譯者注:1. 把原本XY軸的二維平面觸控技術,加入了Z軸的概念,使的得觸控技術多了 XYZ軸的三維觸控思維。 2. Force在星際大戰裡面代表的是原力的意思,最經典的對白就是 Use the Force, Luke!。

雖然在名詞上我們做了一些探討跟著墨,也知道Force Touch跟3D Touch在技術上的本質是相同的。但在實際的應用上3D Touch跟Force Touch 仍然是有區別的!很顯然的3D Touch 在偵測按壓力道的能力比起Force Touch更為敏銳。相較於Force Touch,3D Touch在分辨按壓力度的基礎上可以依照不同的按壓力道加以辨識並定義出更多的級別。

雖然這個觸控方式的革新對許多人來說看起來也許沒那麼重要,但它確實能它允許開發者在iPhone上做出許多更精確的測量跟應用。我們來看看這個叫做Gravity的App,他將iPhone轉換成了支援Force Touch的數位電子秤。雖然他目前在App Store上架的提交申請上,尚被Apple以不明的原因所拒絕。但就創意的角度來說它真的很完美,同時這App也向你展示了3D Touch 是如何運作的,心動了嗎?我們也來打造類似的App吧。

開始打造App

首先, 下載這個我已經做好的專案樣板。基本上,這只是一個給iPhone使用的空白SingleView樣板。在這個樣版裡面,我建立了(UILabelUIImage) 並且已經在ViewController.swift設定好了IBOutlets關聯。

3dtouch-storyboard

我們的應用程式的設計相當簡單:由一個視圖控制器跟兩個標籤構成:一標籤個作為標題使用,另一個標籤將顯示在我們在iPhone上面的按壓力道的百分比。

我們開始coding吧!在iPhone 6s 和6s Plus的開發上,UITouch 物件有兩個新的稱之為:forcemaximumPossibleForceCGFloat屬性。其中force描述了一個觸摸行為的正常力度, 在正常的觸摸行為下平均值會是1.0。maximumPossibleForce 表示每個觸摸行為下面的可能出現的最大力度。

每當用戶觸摸螢幕上的任何物件,touchesBegan的方法將會首先被呼叫、緊接著被呼叫的方法是touchesMoved (如果使用者在屏幕上移動一個或多個手指,然後touchedCancelledtouchesEnded會依照實際的情況被呼叫。) 為了達成我們想要的目的,touchesMoved是我們唯一需要的呼叫的方法(method)。

touchesMoved方法內有兩個參數:toucheseventtouches是一個UITouch物件的集合Set(不同對象的無序集合)。在實際的開發應用上,每一個touch『應該』只會有一個被觸摸的對象,但我們不能100%的確保這樣的事情會一直發生。(比方說我們在設計UI的時候不小心設定了一個觸控區域,但這觸控區域裡面不小心疊放了了兩個以上元件)。所以我們強烈的建議透過使用optional binding來檢查touches.first是否為nil來避免一些不必要的狀況。(註:touches.first是在touches集合內的第一個被觸控的物件)。

所以我們在ViewController.swift加入以下的方法:

override func touchesMoved(touches: Set, withEvent event: UIEvent?) {
    if let touch = touches.first {
        if #available(iOS 9.0, *) {
            if traitCollection.forceTouchCapability == UIForceTouchCapability.Available {
                // 3D Touch capable
            }
        }
    }
}

if語句內,我們先檢查設備是否具備3D觸控能力。如果你正在做這個 project 是為了有趣好玩,這部分是可以選擇性的忽略,你可以不要添加這樣的判斷。如果你打算把應用程序發表到App Store並上架,我們認為這個檢查是必要的,因為像iPhone 6及更早以前的設備是不支援3D觸控的,我們可能會需要排除這些設備。

請注意,我還另外檢查設備上的作業系統是否運行iOS 9.0或以上的版本。我的做法,是透過在Swift 2.0中引入的新#available語法。如果你想了解更多關於新的Swift 2.0 相關得訊息,我建議你閱讀這篇文章。同樣,這檢查也是選擇性的,如果你的最後發布的目標鎖定給iOS 9.0或以上的設備來使用,你可以選擇性的忽略它。

要獲得觸控壓力的百分比,我們可以很簡單的透過取得按壓時的最大力道來計算出最後的結果。(i.e. touch.maximumPossibleForce)就是這次觸摸可能最大的按壓力道。最後更新標籤內的文字。

所以我們改寫之前的方法變成這樣:

override func touchesMoved(touches: Set, withEvent event: UIEvent?) {
    if let touch = touches.first {
        if #available(iOS 9.0, *) {
            if traitCollection.forceTouchCapability == UIForceTouchCapability.Available {
                // 3D Touch capable
                let force = touch.force/touch.maximumPossibleForce
                forceLabel.text = "\(force)% force"
            }
        }
    }
}

如果你在iPhone 6s/6s Plus上執行這個App,他應該會顯示你按壓螢幕力道的百分比。然而我們這邊還想要做一些調整,你可能會想要把手機上面取得的公克數也顯示在iPhone上。根據 Ryan McLeod的文件,感測器最大的回傳重量是385克,超過的重量將會被忽略。所以,maximumPossibleForce可以透過一些計算換算成385克(約3.8牛頓)。 通過簡單的計算,你可以以百分比的力量轉化為公克。所要做的就是回傳我們收到的力量並乘以385,就會得到目前按壓力道的公克值。另外針對超過385克以上的按壓力道,我們最後會改變標籤的顯示,變成類似385克+得顯示方式 。

現在,使用下面的代碼來更新我們先前已經寫好的方法:

override func touchesMoved(touches: Set, withEvent event: UIEvent?) {        
    if let touch = touches.first {
        if #available(iOS 9.0, *) {
            if traitCollection.forceTouchCapability == UIForceTouchCapability.Available {
                if touch.force >= touch.maximumPossibleForce {
                    forceLabel.text = "385+ grams"
                } else {
                    let force = touch.force/touch.maximumPossibleForce
                    let grams = force * 385
                    let roundGrams = Int(grams)
                    forceLabel.text = "\(roundGrams) grams"
                }
            }
        }
    }
}

Cool! 你已完成了一個非常簡單的數位電子秤App了。

3d-touch-scale-app

現在,這個App在我們結束使用的還不會將重量歸零,我們希望在完成觸摸行為,當手指離開螢幕之後能將重量的標籤歸零。所以透過下面代碼來實現 touchesEnded 的方法來,使得標籤可以重置。

override func touchesEnded(touches: Set, withEvent event: UIEvent?) {
    forceLabel.text = "0 gram"
}

主螢幕的Quick Actions

另一個關於3D Touch上很棒的應用是Quick Actions在主螢幕(home screen)上。 Quick actions提供一個快速且便捷的方式可以讓他們在可以快速的在App的功能裡面快速的跳轉。你只要在一個App Icon很用力按壓你就可以看到這個快結方式。由於發表了3D Touch的方式, Twitter 、 Instagram 和一些其他的Apple apps 也陸續的更新他們的軟體並使用這個新功能。.

3d-touch-quick-action

讓我們為我們的scale app新增一個Quick Action吧。我們要新建的功能是讓使用者可以透過Quick Action來開啟我們的App,並且有著藍色的背景跟白色的內文背景。要新增Quick Actions,我們得先開啟專案內的info.plist (點選Scale workspace 在project navigator,接著選擇Scale target 並且跳到Info分頁)。 在檔案中,添加UIApplicationShortcutItems的陣列。每個陣列裡面的元素都是由Quick Action的屬性所構成:

  • UIApplicationShortcutItemType (必填):這是Quick Action 的辨識字串。請注意,此字串必須是唯一的,且為本app專用的(不可以跟其他的App重複)。我們提供一個小建議:你可以使用bundle ID作為UIApplicationShortcutItemType的前綴字串或使用其他如app的唯一識別字串。
  • UIApplicationShortcutItemTitle (必填):這是Quick Action的標題字串,並會顯示給使用者看到。舉例來說我們可以設定為“顯示拍攝最後一張照片”。
  • UIApplicationShortcutItemSubtitle (選填):這是Quick Action在Title下方的說明字串。舉例來說我們可以顯示“昨天最後拍攝的照片”。如果你想要新增icon到的Quick Action,你有兩種方法可以實現:可以使用Apple內建的系統icon或者使用自己的自定義的icon。
  • UIApplicationShortcutItemIconType (選填): 這是Quick Action的IconType字串,如果你想要你定義的Quick Action顯示的為Apple內建的系統icon,可以透過這個參數來指定你想顯示的系統Icon類型。
  • UIApplicationShortcutItemIconFile (選填): 這是Quick Action的IconFile字串,用來指定你想在Quick Action顯示的客製化Icon名稱,這個icon的尺寸為35×35。並且如果設定了本設定值,則前面提到的UIApplicationShortcutItemIconType將會被忽略而不產生作用。
  • UIApplicationShortcutItemUserInfo (optional): 一個 Dictionary 可以提供更多額外的資訊並傳送給Quick Action做更多的應用與處理。

在這個陣列裡面,我們定義了四個”OpenBlue” Quick Action的設定值。所以你的info.plist看起來應該如下所示:

3d-touch-infoplist

請注意,我用$(PRODUCT_BUNDLE_IDENTIFIER)而不是com.appcoda.Scale 或是任何所使用的bundle ID。這是出自於安全考量。 如果因為任何的理由,我在General裡面更改了bundle ID,整個project將會受影響且Bundle ID 將會在所有的地方變更。此外,我將要手動的去更改每個將會變動的地方。在你的info.plist 檔案內,你可以看到很多Bundle Identifier key 的使用方法,而$(PRODUCT_BUNDLE_IDENTIFIER) 這個Bundle Identifier則描述了你專案內的Bundle ID。

我們要做的最後一件事情是實現使用者透過Quick action來開啟這個App的一些處理。 最便捷的方式呼叫AppDelegate.swift 裡面的performActionForShortcutItem方法來處理。

當被App被以Quick Action的方式啟動的時候,這個方法將會被呼叫。所以我們得添加下面的代碼來來處理quick actions方法:

func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
    
    // Handle quick actions
    completionHandler(handleQuickAction(shortcutItem))
    
}

我們的期望是透過程序的呼叫來取得一個布林值,而這個布林的值取決我們是否success / failure的使用quick action行為。另外,我們也建立了一個自己定義的handleQuickAction的函數,來處理快捷方式。

有一個很棒的方式來處理Quick Action,是透過使用建立一個枚舉(enums)描述方式來處理UIApplicationShortcutItemType的原始值。接著我們宣告來實現handleQuickAction的方法來處理剛剛建立的enum,並在App使用quick action來開啟的時候設定被背景顏色為藍色。

enum Shortcut: String {
    case openBlue = "OpenBlue"
}

func handleQuickAction(shortcutItem: UIApplicationShortcutItem) -> Bool {
    
    var quickActionHandled = false
    let type = shortcutItem.type.componentsSeparatedByString(".").last!
    if let shortcutType = Shortcut.init(rawValue: type) {
        switch shortcutType {
        case .openBlue:
            self.window?.backgroundColor = UIColor(red: 151.0/255.0, green: 187.0/255.0, blue: 255.0/255.0, alpha: 1.0)
            quickActionHandled = true
        }
    }
    
    return quickActionHandled
}

這非常簡單不是嗎。如果你現在運行應用程序並使用Quick Action的方式啟動它,背景將會變成藍色的。

3d-touch-scale-blue

一件事要記住

但這裡有一件事情是你必須要記住。在App啟動順序方面,因為Quick Action的加入,這將使得以往我們所知的標準App啟動跟透過Quick Action啟動App變得有所差別。如你所知的,當一個App透過標準的方式來啟動(直接點選App Icon),willFinishLaunchingWithOptionsdidFinishLaunchingWithOptions方法會被呼叫。但是如果一個App是透過Quick Actions來執行,他將會觸發performActionForShortcutItem 方法並執行。

3d-touch-quickaction-methods

你可以看到didFinishLaunchingWithOptions 方法的裡面,我們透過下面一行代碼來設置背景顏色為白色。當然,這是當這個App是通過點選App Icon正常啟動的狀況下。

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions:
    [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.
    self.window?.backgroundColor = UIColor.whiteColor()
        
    return true
}

這就是問題所在: 當你通過quick action啟動應用程序, willFinish, didFinish 會被呼叫,最後是performActionForShortcutItem。這樣的做法其實有點笨,因為背景色被首先設置為白色,之後再變成藍色。雖然最後看起來的節果都唷樣,但這不是我們預期的,當用戶透過quick action來啟動個App時我們並不想設置背景顏色為白色。

為了解決這個問題,我們必須在實體化didFinishLaunchingWithOptions方法中加入條件檢查:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions:
    [NSObject: AnyObject]?) -> Bool {
    print("didFinishLaunchingWithOptions called")
    var isLaunchedFromQuickAction = false
   
    // Check if it's launched from Quick Action
    if let shortcutItem = launchOptions?[UIApplicationLaunchOptionsShortcutItemKey] as? UIApplicationShortcutItem {
        
        isLaunchedFromQuickAction = true
        // Handle the sortcutItem
        handleQuickAction(shortcutItem)
    } else {
        self.window?.backgroundColor = UIColor.whiteColor()
    }
    
    // Return false if the app was launched from a shortcut, so performAction... will not be called.
    return !isLaunchedFromQuickAction
}

要確定應用程序是由一個Quick Action啟動,你可以檢查UIApplicationLaunchOptionsShortcutItemKey這個啟動選項鍵。而UIApplicationShortcutItem物件可作為啟動選項鍵的值。如果App是由一個Quick Action啟動,我們可以很簡單的呼叫handleQuickAction並改變背景為藍色。

因為我們已經在didFinishLaunchingWithOptions處理了quick action要做的所有工作,所以我們不想讓performActionForShortcutItem再次呼叫handleQuickAction函式。所以,最後我們返回值為false ,告訴系統不要再度的呼叫performActionForShortcutItem方法。

好了就是這樣!現在,你可以再次運作並測試我們的應用程序了。Quick Action應該可以運作的很完美了。

結論

3D Touch是一個很棒的方式來提升你的App許多方便性跟便捷性,並且提高App的附加價值。然而你要知道,並非所有的設備都支持3D觸控,儘管這可能會在未來隨著世代的更替或新裝置的普及會逐漸改變,所以在撰寫相關的App的時候你得考慮這點。

在讀完這篇文章後,你應該能夠添加Quick Actions到你自己的iOS應用程序,並用來偵測使用者按壓的力度了。

作為參考,可以下載完整的Xcode項目這裡 。與往常一樣,可以的話針對這封教學或3D Touch請給我一些評論上或分享你的想法。

譯者簡介:黃凱揚-系統分析與資深程式設計師,畢業南台科技大學電子系,曾在人力資源相關領域進行系統開發,13年的系統開發整合經驗,近年來協助客戶行 App 軟體以及 WebBase系統開發與系統分析與整合。

原文:3D Touch Introduction: Building a Digital Scale App and Quick Actions

作者
Maxime Defauw
Maxime Defauw 擁有豐富的程式設計經驗,以往曾在App Store及Google Play Store 發佈多個個人開發的Apps。現年16歲的Max居於比利時,精通Objective-C, C, C#及Swift等多種電腦程式語言。今年的6月,Max獲Apple的贊助出席於在三藩巿舉行的WWDC15!在埋首於程式設計以外的時間,Max還喜歡曲棍球和高爾夫球等運動。
評論
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。