この記事について
せっかく開発したアプリはより多くの人に使ってほしいですよね。
ということで、ローカライズという言語や地域ごとに表示を変える仕組みを紹介したいと思います。
そして、今回は特にiOS10から新しく導入された数量や単位を扱う仕組みについて紹介します。
紹介
Measurements and Unitsを書くときの記述量を少なくするライブラリを作りました。
Measurements & Units とは
iOS10からFoundationフレームワークに追加された、数量と単位を扱う新しい仕組みです。
この仕組みを使うことによって、国や地域ごとの単位の表示を気にすることなくアプリの開発ができるようになります。
という記事も一時期話題になりましたが、
この仕組を使えばアメリカではマイル、日本ではキロメートル、また違う国ではフィートのように簡単に対応することができます👀
ライフログ系、ワークアウト系のアプリには特に相性の良い機能です。
Measurementについて
Measurementは以下のように宣言され、数量と単位
を持ちます。
/// A `Measurement` is a model type that holds a `Double` value associated with a `Unit`.
///
/// Measurements support a large set of operators, including `+`, `-`, `*`, `/`, and a full set of comparison operators.
public struct Measurement<UnitType : Unit> : ReferenceConvertible, Comparable, Equatable {
public typealias ReferenceType = NSMeasurement
/// The unit component of the `Measurement`.
public let unit: UnitType
/// The value component of the `Measurement`.
public var value: Double
/// Create a `Measurement` given a specified value and unit.
public init(value: Double, unit: UnitType)
/// The hash value.
///
/// Hash values are not guaranteed to be equal across different executions of
/// your program. Do not save hash values to use during a future execution.
public var hashValue: Int { get }
}
たとえば、20kmを表現したいときは、valueを20に、unitにキロメートル(UnitLength.kilometers
)を指定します。
Measurement型
の変数同士は(次元
が同じなら)足し算、引き算が可能で、
Double型
の変数とは、掛け算、割り算もすることができます。
let distanceTraveled = Measurement(value: 20, unit: UnitLength.kilometers)
// 20.0 km
let distanceToGo = Measurement(value: 1.975, unit: UnitLength.kilometers)
// 1.975 km
let totalDistance = distanceTraveled + distanceToGo // 21.975 km
let tripleDistance = 3 * distanceToGo // 5.925 km
let halfDistance = distanceToGo / 2 // 0.9875 km
次からは上で出てきた、単位と次元について説明します。
Unitについて
まずは単位(Unit
)についてです。
単位の特徴をまとめます
- 全ての単位は記号を持つ(メートルだったら
m
) - 単位は次元を持つことがある(持たないこともある)
- 同一次元内なら等価な変換を行なうことができる(
m ⇔ ft
)
よって、Unit
の定義を見てみるとString型
のsymbol
(記号)だけが宣言されています。
open class Unit : NSObject, NSCopying, NSSecureCoding {
open var symbol: String { get }
public init(symbol: String)
}
Dimention
次に、次元の話題に移ります。
- 次元は単位の種類を表す(長さ、重さ、速さなど)
- 異なる単位で表すことができる(
km, m, ft, mi
など) - 基本単位を必ず持つ(長さ -> メートル、時間 -> 秒など)
- 同一次元内で単位変換を行なうことができる
Dimension
の定義を見てみると、
単位変換を行なうためのUnitConverter型
のconverter
と、
次元内で基本となる単位であるbaseUnit
を持ちます
open class Dimension : Unit, NSSecureCoding {
@NSCopying open var converter: UnitConverter { get }
public init(symbol: String, converter: UnitConverter)
/*
This class method returns an instance of the dimension class that represents the base unit of that dimension.
e.g.
NSUnitSpeed *metersPerSecond = [NSUnitSpeed baseUnit];
*/
open class func baseUnit() -> Self
}
Dimension
を継承した単位は既に170個以上定義されていて、逐一単位を定義する必要はありません。
面積、加速度、電圧、角度、周波数など国際単位系に準拠した単位をすぐに利用することができます。
Dimensionの良いところ
Dimensionの良いところは同一次元ないなら単位を気にすることなく計算できる点です。
例として先程の変数の一方の単位をフィートに変えてみます。
フィートとメートルは同じ次元なので、自分で変換式を定義することなく計算でき、
結果は長さの基本単位であるメートルで出力されます。
let distanceTraveled = Measurement(value: 2, unit: UnitLength.kilometers)
// 2.0 km
//let distanceToGo = Measurement(value: 1.975, unit: UnitLength.kilometers)
//let totalDistance = distanceTraveled + distanceToGo
let distanceToGo = Measurement(value: 11.3, unit: UnitLength.feet)
// 11.3 ft
let totalDistance = distanceTraveled + distanceToGo
// 2003.44424 m
MeasurementFormatter
最後にこれらの値を表示する仕組みについて説明します。
いままでもFormatterといえば、NumberFormatter、DateFormatterなどがありましたが、そこに新たに仲間入りしたのがMeasurementFormatterです。
public class MeasurementFormatter : Formatter {
public var unitOptions: MeasurementFormatter.UnitOptions
public var unitStyle: Formatter.UnitStyle
@NSCopying public var locale: Locale!
@NSCopying public var numberFormatter: NumberFormatter!
public func string(from measurement: Measurement<Unit>) -> String
public func string(from unit: Unit) -> String
}
MeasurementFommatterはデフォルトで現在の地域の単位が優先されるようになっています。
たとえば、valueを5、単位をマイルで設定したdistanceは、現在の設定地域が日本のとき8.047 kmと自動的に変換されて表示されます!!
let formatter = MeasurementFormatter()
let distance = Measurement(value: 5, unit: UnitLength.miles)
formatter.string(from: distance) // 8.047 km
unitOptions
やunitStyle
を用いればより詳細な指定をすることもできます。
UnitStyle
short
、medium
、long
の三種類があり、単位の表示が変わります
/*
* There are 3 widths: long, medium, and short.
* For example, for English, when formatting "3 pounds"
* Long is "3 pounds"; medium is "3 lb"; short is "3#";
*/
@available(iOS 8.0, *)
public enum UnitStyle : Int {
case short
case medium
case long
}
UnitOptions
public struct UnitOptions : OptionSet {
public init(rawValue: UInt)
public static var providedUnit: MeasurementFormatter.UnitOptions { get }
public static var naturalScale: MeasurementFormatter.UnitOptions { get }
public static var temperatureWithoutUnit: MeasurementFormatter.UnitOptions { get }
}
-
.providedUnit
は指定された単位で表示したいときに使います -
.naturalScale
は"自然な表示"が行なわれます(AppleWatchなどの小さい画面に表示されるときに便利) -
.temperatureWithoutUnit
はC
やF
などの単位なしに温度を表示するときに使います
まとめ
このiOS10から導入されたMeasurements & Unitsという仕組みを使えば、開発者が単位変換に煩わされることなく数量の表示、変換、ローカライズをすることができ、よりユーザーにやさしいアプリにすることができると思います。
ぜひ参考にしてみてください!
発展編
- Measurement
- Unit
- Dimension
- MeasurementFormatter
の4つで基本的な実装は可能なのですが、「アプリ内で独自の単位を作りたい!」などの要求が出てきたときに対応するために発展編を用意しました。
発展編 - カスタム単位を作る
メートルやフィートなどが出てきましたが日本古来の単位がない、、と思ったので作ってみましょう。
例として、今回は単位「尺」を作ってみたいと思います。
によると、「尺」は33分の10メートルらしいです。
なので、「尺」は以下のように定義できます。
extension UnitLength {
class var syaku: UnitLength {
return UnitLength(symbol: "尺",
converter: UnitConverterLinear(coefficient: 10/33))
}
}
上のコードを説明すると、
「尺」の次元は長さ([L]
)なので、UnitLength
内で宣言します。
そして、記号は"尺"なので、symbolには"尺"を指定します。
さらに、1尺 ⇔ 10/33 m
なのでconverterにはUnitConverterLinear(coefficient: 10/33))
を指定します。
このように宣言するだけで、
let lengthInShayku = Measurement(value: 10, unit: UnitLength.syaku)
// 10.0 尺
let lengthInYards = lengthInShayku.converted(to: UnitLength.yards)
// 3.31397969193245 yd
let lengthInFeet = Measurement(value: 5.3, unit: UnitLength.feet)
// 5.3 ft
lengthInShayku + lengthInFeet
// 4.64574303030303 m
単位「尺」を持つ変数を宣言したり、
その値をヤードに変換したり、
他のUnitLength
の単位を持つ変数との演算が可能になります!👀
さきほどから登場しているUnitConverter
について少し説明したいと思います。
UnitConverter
UnitConverterは同一次元内でbaseUnitと他の単位の変換を担うものです。
UnitConverterとそれを継承したUnitConverterLinearの2種類があります。
単位の変換はほとんど線形なので後者を使うことが多いと思います。
さきほどの「尺」の例だと、定数はなく
[mの値] = 10/33 * [尺の値]
なので、coefficient(係数)に 10/33を指定しました。
また、華氏[F]と摂氏[C]の変換だと、
F = 9/5 * C + 32
となるので、係数に9/5、定数に32を指定すればよいということになります。
y = ax + bという1次関数を思い出して、以下の式に当てはめる。
[baseUnitValue] = coefficient * [value] + constant
発展編 - カスタム次元を作る
カスタム単位の作り方の次はカスタム次元を作ってみましょう。
カスタム次元の例として、ある出来事の感激度、印象度を考えてみました。
ポイントはDimensionを継承し、baseUnitを定義することです。
class UnitImpression : Dimension {
class var good: UnitImpression {
return UnitImpression(symbol: "👍", converter: UnitConverterLinear(coefficient: 1))
}
class var great: UnitImpression {
return UnitImpression(symbol: "👏", converter: UnitConverterLinear(coefficient: 10))
}
class var wonderful: UnitImpression {
return UnitImpression(symbol: "😘", converter: UnitConverterLinear(coefficient: 50))
}
class var lifeChanging: UnitImpression {
return UnitImpression(symbol: "😍", converter: UnitConverterLinear(coefficient: 2000))
}
override class func baseUnit() -> UnitImpression {
return good
}
}
発展編 - アプリに組み込む
たとえば、プレゼンのまとめアプリを作るとしましょう。
それに必要なPresentation
型を定義してみます。
発表者の名前、タイトル、プレゼン中に動いた距離、発表時間、印象度を入れてみました。
それに加えて、プレゼン中にどれだけ動いたかを測る指標も加えてみました。
これを組み込むのもMeasuremetの仕組みを使えば元の単位を気にすることなく簡単に定義することができます。
struct Presentation {
let speaker : String
let title : String
let movement : Measurement<UnitLength>
let duration : Measurement<UnitDuration>
let impression : Measurement<UnitImpression>
var movementRate: Measurement<UnitSpeed> {
let distanceInMeters = movement.converted(to: .meters)
let durationInSeconds = duration.converted(to: .seconds)
return Measurement(
value: distanceInMeters.value / durationInSeconds.value,
unit: UnitSpeed.metersPerSecond)
}
}
使ってみましょう。
let presentation =
Presentation(
speaker: "ken0nek",
title: "Measurements & Units",
movement: Measurement(value: 60, unit: UnitLength.centimeters),
duration: Measurement(value: 10, unit: UnitDuration.minutes),
impression: Measurement(value: 100, unit: UnitImpression.lifeChanging))
presentation.movementRate // 000.1 m/s
presentation.impression // 100.0 😍
presentation.impression.converted(to: UnitImpression.baseUnit()) // 50000.0 👍
presentation.movement.converted(to: UnitLength.syaku) // 1.98 尺
以上のように、単位を含めた値の取得、単位の変換が簡単にできました。
発展編 - まとめ
発展編では、カスタム単位やカスタム次元の作り方、それのアプリへの組み込み方を紹介しました。
少し長く難しい説明になってしまったかと思いますが参考になれば幸いです。
[iOSアプリケーション開発コースメンター: とみー]
※ 本記事はWWDC2016で公開されている情報をもとに掲載しております。
※ 本記事は以下の発表をQiitaにまとめたものです。
参考
- Measurement - Apple Developer Documentation
- Unit - Apple Developer Documentation
- Dimension - Apple Developer Documentation
- UnitConverter - Apple Developer Documentation
- UnitConverterLinear - Apple Developer Documentation
- MeasurementFormatter - Apple Developer Documentation
- About Internationalization and Localization
- Measurements and Units - WWDC 2016 - Videos - Apple Developer
- 238_measurements_and_units.pdf