《iOS 10 day by day》是 shinobicontrols 公司編寫的系列博客,介紹開發(fā)者需要了解的 iOS 10 新特性洗贰,每周更新污淋。本系列翻譯(文集地址)已取得官方授權柿隙。目錄點此叶洞。倉薯翻譯,歡迎指正:)
Shinobicontrols 為 iOS 和 Android 開發(fā)者提供高性能禀崖、響應式的 UI 控件 SDK衩辟,尤其是圖表方面的控件。 官網(wǎng) : shinobicontrols.com twitter : @shinobicontrols
本文翻譯時參考了 simpletonking 的同一篇譯文波附,在此感謝艺晴。Thank you simpletonking!
本周我們來看看新的 Measurement API,這是 Foundation 框架新增的一部分掸屡。這套 API 從表面上看平凡無奇封寞,就是提供了一套單位換算的方法,例如公里與英里互相換算仅财。
不過狈究,仔細想想,我們確實在單位換算上浪費了太多時間盏求。比如谦炒,你有一個角度值,但是用來旋轉(zhuǎn) view 的 API 只接受弧度值风喇。或者缕探,可能你的 app 里距離的計量單位是英里魂莫,但是要為了習慣用公里的用戶轉(zhuǎn)換成公里去顯示。
在 iOS 10 之前爹耗,你可能已經(jīng)自己寫了一些單位換算的方法耙考,或者用了第三方庫。現(xiàn)在蘋果提供了新的 API 潭兽,能解決大部分問題了倦始。我們來看看它具體能做什么吧。
基本概念
本文使用 Swift 3 編寫山卦,在 Xcode 8 GM build 上編譯運行鞋邑。
我們要介紹一個單位空間(dimension)的概念,用 Measurement 的 model 類型把數(shù)值保存為某個單位空間的數(shù)據(jù)。所謂“單位空間”就是一組能互相轉(zhuǎn)換的單位枚碗,比如克可以轉(zhuǎn)換成千克逾一,千克也能轉(zhuǎn)換回來。每個單位空間都有一個基本單位肮雨,其他單位都用這個基本單位來表示(比如遵堵,容積的基本單位是升,而 1 毫升就是 0.001 升)怨规。
建立度量值
先從簡單的開始陌宿,假設我們有一品脫牛奶,想知道一品脫是多少公升波丰。代碼如下:
let milk = Measurement(value: 1, unit: UnitVolume.imperialPints)
milk.converted(to: .liters)
// 輸出 0.568261 L
很簡單吧壳坪!定義出了某個單位的度量值之后,就只能把它換算為同一個單位空間的其他單位呀舔。converted
這一步會自動進行類型檢查弥虐,milk
變量的類型是 Measurement<UnitVolume>
,換算成的單位也要屬于 UnitVolume
這個單位空間媚赖。只能在同一個單位空間之內(nèi)互相換算霜瘪,這種限制是顯而易見的——不然,把公升換算成英里怎么換算惧磺?
運算符
Measurement API 支持對度量值使用運算符颖对。
如果我們想要 5 品脫的牛奶,就可以寫:
let fivePints = milk * 5
這樣會創(chuàng)建一個新的度量值磨隘,接下來我們就可以把它換算成另一個單位:
fivePints.converted(to: .cups)
// 輸出 11.8387708333333 cup
可以注意到缤底,把代碼放在 Playground 里,或者把度量值打印出來番捂,末尾會自動帶上它的單位个唧。
當然,能用的運算符不只有乘號设预。還有其他的幾種徙歼,比如雙等號 ==
:
let kms = Measurement(value: 5, unit: UnitLength.kilometers)
let meters = Measurement(value: 5000, unit: UnitLength.meters)
kms == meters // true
以及加號:
kms + meters // 10000.0 m
Formatter
前面提到過,做本地化的時候我們經(jīng)常需要為不同的 locale 顯示不同的單位鳖枕。
除了新的 Measurement API魄梯,蘋果還提供了 MeasurementFormatter
,它能把度量值轉(zhuǎn)化成格式化字符串宾符。
默認情況下酿秸,measurement formatter 使用的是用戶當前的 locale。下面我們手動更改這一點魏烫,更改前后分別打印同一段兩個城市之間的距離辣苏,看看輸出有什么變化:
let newcastleToLondon = Measurement(value: 248, unit: UnitLength.miles)
let formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "fr")
formatter.string(from: newcastleToLondon) // 輸出 399,166 km
formatter.locale = Locale(identifier: "en_GB")
formatter.string(from: newcastleToLondon) // 輸出 248 mi
好棒肝箱!不費吹灰之力就完成了。
項目
我們已經(jīng)大略看了一下 API 的基本用法考润,下面來上手玩一下吧狭园。
我們來做一個小風車,轉(zhuǎn)動速度與風力強度成比例糊治,而風力強度可以用一個滑動條來調(diào)節(jié)唱矛。
風車就是一個簡單的 UIView 子類。把它添加到 UIViewController 的 view 上井辜,再加一些其他的基本 UI 控件:一個調(diào)節(jié)風速的滑動條绎谦,還有一個用 米/秒 和 英里/小時 兩種單位顯示風速的 label。如果想看完整的 playground粥脚,歡迎從 GitHub 下載窃肠。
我們把目光集中在用到 Measurement API 的部分上:首先是拖動滑動條的時候,在 label 上顯示風速:
func handleWindSpeedChange(slider: UISlider) {
let windSpeed = Measurement(value: Double(slider.value), unit: UnitSpeed.metersPerSecond)
let milesPerHour = windSpeed.converted(to: .milesPerHour)
windSpeedLabel.text = "Wind speed: \(windSpeed) (\(milesPerHour))"
}
label 的顯示就像下面這樣:
哇哦刷允!只是一個簡單的 demo 而已冤留,不需要弄得這么精確。有時候小數(shù)點后的位數(shù)顯示得太多树灶,都看不到后面的單位 m/s 了纤怒。要解決這個問題,我們可以使用上面提過的 MeasurementFormatter
天通。
let windSpeed = Measurement(value: Double(slider.value), unit: UnitSpeed.metersPerSecond)
let measurementFormatter: MeasurementFormatter = {
let formatter = MeasurementFormatter()
formatter.unitOptions = .providedUnit
let numberFormatter = NumberFormatter()
numberFormatter.minimumIntegerDigits = 1
numberFormatter.minimumFractionDigits = 1
numberFormatter.maximumFractionDigits = 1
formatter.numberFormatter = numberFormatter
return formatter
}()
let metersPerSecond = measurementFormatter.string(from: windSpeed)
let milesPerHour = measurementFormatter.string(from: windSpeed.converted(to: .milesPerHour))
windSpeedLabel.text = "Wind speed: \(metersPerSecond) (\(milesPerHour))"
創(chuàng)建 formatter 的時候泊窘,我們要指定它使用 providedUnit
。這是為了防止 formatter 忽略我們指定的單位像寒,用它自己認為合適的格式輸出烘豹。對我們現(xiàn)在的情況來說,如果不設這個 option诺祸,formatter 會對兩個結(jié)果都用 英里/小時 為單位輸出携悯。
MeasurementFormatter 本身包含另一個 formatter(有點像嵌套的 formatter!),內(nèi)層的 formatter 是用來格式化數(shù)字部分的筷笨。我們要求數(shù)字顯示為一位(且僅一位)小數(shù)蚌卤。
最后,我們使用構造好的 formatter奥秆,將 米/秒 和 英里/每小時 的兩種風速度量分別格式化為 string。
為了有一點視覺反饋咸灿,我們希望葉片轉(zhuǎn)動的速度能隨著風速改變(要注意构订,下面用到的這些數(shù)值只是為了展示用的,跟風力的物理學基礎沒有任何關系)避矢。
顯示動畫的是 TurbineView
悼瘾,不過我們要把每秒鐘葉片轉(zhuǎn)動多少角度的數(shù)值傳給它囊榜。你可能會想到去定義一個屬性,類似這樣:
/// 葉片每秒轉(zhuǎn)動的角度亥宿,用弧度的形式表示
public var bladeRotationPerSecond: Double
這樣定義沒什么問題卸勺,也跟蘋果官方的 API 保持一致,角度是用弧度表示的烫扼。不過曙求,怎么防止使用者無意中傳了角度值而不是弧度值呢?你可能會說:“他們應該好好看看文檔”映企。這句話有一定道理悟狱,不過萬一這個屬性沒有文檔呢?并且堰氓,因為我們平常更習慣用角度值挤渐,所以這也是個容易不小心犯下的錯誤。
那我們能怎么利用上 Measurement 框架双絮,只約束使用者傳過來的是表示角度的值浴麻,具體單位不限呢?這樣使用者想傳弧度或者角度都可以囤攀,反正都會自動轉(zhuǎn)化成我們需要的單位软免。聽起來很棒,我們試試吧:
// TurbineView 的屬性
public var bladeRotationPerSecond: Measurement<UnitAngle> = Measurement(value: 0, unit: UnitAngle.degrees) {
didSet {
rotate()
}
}
在 viewController 里抚岗,我們可以用下面這段代碼來計算一秒旋轉(zhuǎn)多少角度或杠。
func calculateTurbineRotation() {
// 假設滑動條拖到最快時,轉(zhuǎn)速達到每秒 1 圈
let ratio = windSpeedSlider.value / windSpeedSlider.maximumValue
let fullRotation = Measurement(value: 360, unit: UnitAngle.degrees)
let rotationAnglePerSecond = fullRotation * Double(ratio)
turbine.bladeRotationPerSecond = rotationAnglePerSecond
}
參數(shù)想傳角度或者弧度都可以——我選擇了角度宣蔚。然后根據(jù)當前風速來計算旋轉(zhuǎn)角度(如果滑動條的 value
為 0向抢,ratio
就是 0 / 40 = 0;拖到最快的一端胚委,滑動條的 value
是 40挟鸠,ratio
就是 40 / 40 = 1),用到了乘法操作符亩冬,非常方便艘希。
下面來欣賞我們美麗的風車吧:
擴展閱讀
本文中我們用到了蘋果提供的幾組單位,不過這些只是冰山一角硅急;蘋果一共提供了 170 多種不同的單位覆享。你需要用的單位大概率就在其中,不過营袜,如果真的沒有撒顿,也可以自己創(chuàng)建一個。想知道怎么創(chuàng)建(還有其他內(nèi)容<园濉)凤壁,請看這部 WWDC 視頻吩屹。
原作者:Sam Burnstone @sam_burnstone
ShinobiControls 官網(wǎng):ShinobiControls.com twitter : @shinobicontrols
譯者:戴倉薯,本文翻譯時參考了 simpletonking 的同一篇譯文拧抖,非常感謝~