作者:Maxime Defauw,原文鏈接,原文日期:2015/11/09
譯者:saitjr;校對:numbbbbb;定稿:numbbbbb
隨著 iPhone6s 與 6s plus 的到來,蘋果給我們展現了一種全新的交互方式:重按手勢。你可能知道,這個特性已經在 Apple Watch 和 MacBook 上推出了,不過那時叫 Force Touch,就是字面上的意思,給用戶的交互添加一種新的維度。
如果你很好奇 iPhone 的 Force Touch 為啥要更名為 3D Touch,那告訴你吧,you're not alone(譯者注:請用 MJ 的調子唱出來…)。不久前,之前也對這名字糾結不已的 Craig Federighi(譯者注:Apple 高級副總裁)介紹了這個新特性,第一條微博就這樣產生了。也不知道 Force Touch 這名字有啥不好的,就因為有太多星球大戰的梗?(譯者注:其實我不知道這梗...)(校對注:譯者是個妹子)(定稿注:還是單身)
但是,Force Touch 和 3d Touch 確實不一樣!Force Touch 只能識別重按。這方面 3D Touch 要靈敏多了,它能夠識別按壓的力度。
雖然說,這點不同看起來無足輕重,但是這使開發者能開發更多精確計量方面的 App。比如這一款名為Gravity的應用,它利用 Force Touch 讓你的 iPhone 成為了一個電子秤。雖然這款 App 被 Apple 拒了,但是這創意簡直太棒了。為了展示 3D Touch 的工作流程,我們來做一個簡單的 App。
先去下載這個初始案例。初始案例中只有一個空的 Single View。我在里面創建了 App 必要的 UI 元素(UILabel
和UIImage
),并關聯了ViewController.swift
。
這個 App 的設計很簡單:ViewController 上有兩個 Label:一個標題和一個顯示按壓百分比的文本。
那...開始寫代碼吧!在 iPhone6s 和 6s Plus 上,UITouch
對象多了兩個CGFloat
類型的屬性,分別是force
和maximumPossibleForce
。force
表示按得有多重,1.0
表示常規狀態的值。maximumPossibleForce
表示能承受的最大壓力值。
無論什么情況,當用戶觸摸屏幕時,touchesBegan
方法會被調用,接著就是touchesMoved
(如果用戶手指在屏幕上滑動,那么touchedCancelled
與TouchesEnded
也會被調用)。在這個App中,我們只需要關注touchesMoved
方法。touchesMoved
有兩個參數:touches
和event
。touches
是一個裝著UITouch
對象的NSSet
類型集合(集合無序,并且無重復)。我們必須要確保在touches
中只有一個UITouch
對象,但也有考慮不完全的時候,所以強烈建議大家先利用可選綁定來判斷touches.first
(touches
中的第一個UITouch
對象)是否是空。在ViewController.swift
中添加以下代碼:
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
if let touch = touches.first {
if #available(iOS 9.0, *) {
if traitCollection.forceTouchCapability == UIForceTouchCapability.Available {
// 3D Touch capable
}
}
}
}
在這個if
判斷中,還需要添加判斷當前設備是否支持 3D Touch 的代碼。如果你只是做來玩,那就沒必要驗證。但是,如果是要上架的 App,那就必須要判斷,畢竟像 iPhone6 這些舊設備不支持 3D Touch。
除此之外,我還使用了#available
語句(Swift 2.0)對當前系統是否是 iOS9+ 做了判斷。(如果你想學習更多 Swift 2.0 相關的知識,我就更加推薦你閱讀這篇文章了。)同樣,如果你的編譯環境是 iOS9.0+,那么這個判斷可以省略。
要得到按壓百分比?那太簡單了,只需要用force
屬性除以maximumPossibleForce
就可以了(例如:touch.maximumPossibleForce
),maximumPossibleForce
表示能承受的最大壓力值。然后,更新文本:
override func touchesMoved(touches: Set<UITouch>, 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"
}
}
}
}
如果你在 iPhone6s/6s Plus 上跑這個程序,按屏幕時就能看到壓力百分比了。但是,其實我們更想知道放在 iPhone 上物體的重量,而不是百分比。根據Ryan McLeod的 App 可以知道,傳感器的計量范圍的最大值是 385g。因此,maximumPossibleForce
就相當于 385g(相當于3.8N)。通過簡單的計算,就可以把壓力百分比轉為克。需要做的僅僅是用百分比*385。對于重于 385g 的物體,就把 label 改成類似于“385+ grams”這樣的文本好了。
到此,touchesMoved
方法中的代碼更新為:
override func touchesMoved(touches: Set<UITouch>, 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"
}
}
}
}
}
然后...你就有了一個電子秤 App...
還有一個小問題:當物體或者觸摸事件結束之后,文本沒有重置。你可以實現touchesEnded
方法來達到效果:
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
forceLabel.text = "0 gram"
}
主屏幕上的快捷操作
另一個 3D Touch 的用法是主屏幕上的快捷操作。快捷操作可以讓用戶從快捷方式直接跳轉到 App 的某個地方。按壓 App icon 快捷方式就會出現。在介紹 3D Touch 的時候,Twitter、Instagram 等 App 就展示了這個新特性。
讓我們來給剛才的電子秤 App 添加一個快捷操作吧(把白色背景換成藍色)。要添加快捷操作,先打開工程目錄中的info.plist
(在導航欄上點擊工程名,在TARTGET
中找到info
選項卡)。在這個文件中,添加UIApplicationShortcutItems
數組。數組中的元素是包含一個快捷操作配置的字典:
-
UIApplicationShortcutItemType
(必填):快捷操作的唯一標識符(String 類型)。建議將 bundle ID 或者其他唯一字符串作為標識符前綴。 -
UIApplicationShortcutItemTitle
(必填):相當于快捷操作的 title(String 類型),用戶可以看到。例如“顯示最近一張照片”之類的文本。 -
UIApplicationShortcutItemSubtitle
(可選):快捷操作的副標題(String 類型)。例如“昨天拍攝的照片”。如果你想要給快捷操作添加一個 icon,可以自定義,也可以使用系統自帶的。 -
UIApplicationShortcutItemIconType
(可選):表示你要選擇哪種系統圖標作為快捷操作的 icon(String 類型)。 -
UIApplicationShortcutItemIconFile
(可選):表示給快捷操作添加自定義 icon(String 類型)。 -
UIApplicationShortcutItemUserInfo
(可選):在快捷操作交互時傳遞的額外信息(譯者注:類似于通知的 UserInfo 參數)(Dictionary 類型)。
在這個數組中,我們將會給自定義的快捷操作添加 4 個配置。然后,你的info.plist
文件看起來應該是這樣滴:
> 注意,我用到了`$(PRODUCT_BUNDLE_IDENTIFIER)`來代替`com.appcoda.Scale`(就是替代的 bundle ID)。這是出于安全考慮:無論在什么情況下,如果我在`General`中修改了 bundle ID,那整個工程的 bundle ID 就都變了,這勢必會給項目帶來不曉得影響。這樣的話,我就需要手動去修改每個 bundle ID。在`info.plist`里面可以看到,其實每個 Bundle Identifier 配置項都是用的`$(PRODUCT_BUNDLE_IDENTIFIER)`來表示 bundle ID 在工程中的路徑。
最后一件事,就是實現用戶觸發快捷操作的處理流程。快捷方式需要在AppDelegate.swift
的performActionForShortcutItem
方法中處理。當使用快捷操作啟動時,這個方法會被調用。所以,實現這個方法,并在方法中處理快捷操作:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
// Handle quick actions
completionHandler(handleQuickAction(shortcutItem))
}
這個方法需要調用completionHandler
,并傳入布爾值,這個布爾值取決于快捷操作成功與否。這里我們封裝了一個handleQuickAction
方法來處理快捷方式。如果有多個快捷操作,最好的方式是使用枚舉,UIApplicationShortcutItemType
作為枚舉的rawValue
(譯者注:對枚舉不熟悉可以參考這篇文章)。定義一個枚舉,并實現handleQuickAction
方法,在方法中修改背景色為藍色。
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
}
一切都是這么簡單。現在把程序跑起來,使用快捷操作來啟動 App,就可以看到背景已經是藍色了。
還有一件事
還有一個問題你別忘了...在程序啟動順序方面,啟動程序和使用快捷操作喚醒是有區別的。我們都知道,程序啟動會調用willFinishLaunchingWithOptions
和didFinishLaunchingWithOptions
方法。但是當使用快捷操作喚醒時,只會觸發performActionForShortcutItem
方法(譯者注:這就意味著,使用快捷操作來啟動會走三個方法,而使用快捷操作喚醒只會走一個,具體的方法列表如下圖)。
如果你回頭看didFinishLaunchingWithOptions
方法,會發現里面我寫了一行設置背景色為白色的代碼。這個是在直接啟動程序時用的。
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
self.window?.backgroundColor = UIColor.whiteColor()
return true
}
問題來了:當使用快捷操作喚醒程序時,willFinish
,didFinish
和performActionForShortcutItem
都會被調用。所以背景色會先設置成白色,接著又被設置成了藍色。顯然你不想在使用快捷操作啟動時,背景色被設置成白色。
要解決這個問題,我們需要在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
}
通過判斷可選值的UIApplicationLaunchOptionsShortcutItemKey
得到用戶是否是通過快捷操作啟動。UIApplicationShortcutItem
可以作為可選值的類型。如果程序是通過快捷操作啟動的,我們可以直接調用handleQuickAction
方法將背景色改為藍色。
因為我們已經在didFinishLaunchingWithOption
方法中調用了handleQuickAction
,所以沒必要再在performActionForShortcutItem
方法中調用一次。所以最后我們返回了一個false
,告訴系統不要再去調用performActionForShortcutItem
方法。
再次運行程序!完美!
最后
3D Touch 是給程序添加另一種交互方式的好方法。但是你還是不要忘了,目前還不是所有設備都支持 3D Touch。
通過這篇文章,你應該能給你的 App 添加快捷操作,也能計量按壓力度了。
順便,你可以在這里下載程序的最終版。同樣,歡迎大家留言。
本文由 SwiftGG 翻譯組翻譯,已經獲得作者翻譯授權,最新文章請訪問 http://swift.gg。