Formatters: データをヒューマンフレンドリーに – WWDC2020

Session概要

時間の節約とフラストレーションの軽減:アプリに日付、時間、測定結果、名前、リスト、数字、文字列などのデータを表示する際に、それを正しくフォーマットして優れたエクスペリエンスを提供する方法を学びましょう。
このセッションではFormatter APIと、stringsdictでSwiftUIが機能する仕組みを紹介し、データのフォーマットに関する手間のかかる作業の実行にそれらがどのように役立つか、また、ベストプラクティスと、よくあるミスの回避方法についても紹介します https://developer.apple.com/videos/play/wwdc2020/10160/

背景

アプリがどのような言語をサポートしているか、および複数の異なる言語を利用可能かに関わらず、アプリで表示されるデータがどのような形式で表示されるのかを考慮することは重要です
そして、Formatterを強化する基本的なアルゴリズムとデータセットをアップデートし、言語と地域の組み合わせに対するサポートを拡大した Formatter APIを使用することで得られるメリット、それにより節約できる労力と時間、APIを使用し多用途のUIを構築する方法について紹介していきます

Formatについて

Dates and times

  • 日付のフォーマットテンプレート一覧
    • 詳細情報およびサンプル
    • フォーマットが同じでも言語またはロケールごとに表示のされ方が異なることに注意しましょう
  • テンプレートにおいて注意すべきことはフィールドの順序は重要ではないということ
    • dMMMEEEEMMMdEEEE も同じ表示となります
    • テンプレート(表示形式)を渡せばFormatter がテンプレートを元に構築するため
  • DateFormatterdateFormatというプロパティを持ちますが、テンプレートをプロパティに直接セットしてはいけません
// Dates and Times
// Date timeの例、最終的に下記のような形式になる

// Date with Day/Month/Year and Time
// 例:June 22, 2020 at 9:41 AM
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
// .shortを入れると 「at」という単語が自動的に追加される
dateFormatter.timeStyle = .short
dateFormatter.string(from: Date())

// Day of Week + Date + Month
// 年を含まず、月・曜日を表示したいケースの日付
// 例:Friday, June 5
let dateFormatter = DateFormatter()
dateFormatter.setLocalizedDateFormatFromTemplate
    ("MMMMdEEEE")
dateFormatter.string(from: Date())

// Abbreviated Day of Week
// 曜日の省略形のみ表示するケース
// 例:S, M, T ,W, T, F, S
let dateFormatter = DateFormatter()
dateFormatter.setLocalizedDateFormatFromTemplate
    ("ccccc")
dateFormatter.string(from: Date())
// Dates and Times

// Date and Time Components
// DateComponentsFormatterは 2h 26m など期間を表示するようなフォーマット指定に役立ちます
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .abbreviated
let components = DateComponents(hour: 2, minute: 26)
formatter.string(from: components)

// Date and Time Intervals
// May31-June6 など
let formatter = DateIntervalFormatter()
formatter.dateTemplate = "dMMM"
formatter.string(from: startDate, to: endDate)

// Relative Dates and Times
// 自然な方法で過去や未来の日付を表示します
let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .named
formatter.localizedString(from: DateComponents(day: -1))

Measurements

  • アプリで何かしらの計測を使用する場合、 MeasurementFormatter が有用
  • MeasurementFormatterは単位にとどまらず、言語とロケールにわたってすべて行います
  • 計測における単位はAPIで数多く提供されていますが、期待する単位がない場合はカスタム単位の作成が可能
// Measurements

// Temperature
// 温度の指定
let formatter = MeasurementFormatter()
let temperature = Measurement<UnitTemperature>
    (value: 16, unit: .celsius)
formatter.numberFormatter.maximumFractionDigits = 0
formatter.string(from: temperature)

// Speed
let speed = Measurement<UnitSpeed>
    (value: 14, unit: .kilometersPerHour)
formatter.string(from: speed)

// Pressure
let pressure = Measurement<UnitPressure>
    (value: 1.01885, unit: .bars)
formatter.string(from: pressure)

Names

  • PersonNameComponentsFormatter(NameFormatter)を使用することで適切な実装が可能
  • デフォルトのstyleはミディアムが適用され、ユーザの好みに合わせてスタイルを変更可能
  • 2つ(まれに3つ以上)の文字や書記素を組み合わせた記号(モノグラム)のような省略形も表現可能
    • モノグラムは写真やアバターがない場合に最適な選択肢で、UIを親しみやすく魅力的なものにする
    • ただし、指定する文字列の長さによっては省略しきれずUIからはみ出す可能性があるため、文字列の長さをチェックして適切に収まるのか確認することが重要
  • NameFormatterはユーザの設定情報等を使用する
  • 例えば、名前は英語と日本語で姓と名が逆の表記となるため、適切に表示するために下記の項目を考慮する
    • 言語設定
    • 名前、名前の順序
    • 連絡先設定
// Names

let formatter = PersonNameComponentsFormatter()
var nameComponents = PersonNameComponents()
nameComponents.familyName = "Iwasaki"
nameComponents.givenName = "Akiya"
nameComponents.nickname = "Aki-chan"

// Full Name
formatter.string(from: nameComponents)

// Short Name: Respects User Preferences
formatter.style = .short
formatter.string(from: nameComponents)

// Abbreviated Name
formatter.style = .abbreviated
formatter.string(from: nameComponents)
// Abbreviated Name: Monogram
// 文字列の長さをチェックして、モノグラムとして表示するかどうかを決める
formatter.style = .abbreviated
let monogram = formatter.string(from: nameComponents)
if (monogram.count <= 2) {
    // Use Monogram
}
else {
    // Use Icon
}
// Names

let formatter = PersonNameComponentsFormatter()
var nameComponents = PersonNameComponents()
nameComponents.familyName = "岩崎"
nameComponents.givenName = "晃也"
nameComponents.nickname = "あきちゃん"

// Full Name
formatter.string(from: nameComponents)

// Short Name: Respects User Preferences
formatter.style = .short
formatter.string(from: nameComponents)

// Abbreviated Name
formatter.style = .abbreviated
formatter.string(from: nameComponents)

Lists

  • ListFormatterを使用することで、実装における時間を節約するだけでなく、微妙な差異を正しくすることでアプリの見た目を洗練することができます
  • 例えば、下記のように文字列の配列を結合させ、ユーザのロケール設定に合わせて文言を変更することが可能
    • ロケールが英語の場合、 English and Frenchなどandという文字が表現できる
  • iOS 14ではListFormatterは文法のルールを順守するよう更新された
  • ローカリゼーションをAPIによってサポート可能なのはFormatterの利点の一つ
// Lists

// English Localization

let items = [ "English", "French", "Spanish" ] ListFormatter.localizedString(byJoining: items)

let items = [ "English", "Spanish" ] ListFormatter.localizedString(byJoining: items)

let items = [ "Spanish", "English" ] ListFormatter.localizedString(byJoining: items)

// Spanish Localization

let items = [ "Inglés", "Español" ] ListFormatter.localizedString(byJoining: items)

let items = [ "Español", "Inglés" ] ListFormatter.localizedString(byJoining: items)

Numbers

// Numbers

let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.string(from: 32.768) // French (France)

let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.string(from: 32.768) // Arabic (Egypt)

// パーセント表記などの特殊文字もローカライズ可能
formatter.percentSymbol

formatter.decimalSeparator
// Numbers

// 71%
let formatter = NumberFormatter()
formatter.numberStyle = .percent
formatter.string(from: 0.71) // English (US)

// %71
let formatter = NumberFormatter()
formatter.numberStyle = .percent
formatter.string(from: 0.71) // Turkish (Turkey)

Strings

// Strings

var body: some View {
    Text("\(photosCount) Photos Selected")
}

公式サンプルアプリ

下記のような様々なFormatterを試すことができる公式サンプルアプリが提供されているので、触ってみると面白そうです
https://developer.apple.com/documentation/foundation/formatter/displaying_human-friendly_content
最新情報をチェックしよう!