[Swift] FormatStyle の使い方
[Swift] ParseableFormatStyle の使い方
[Swift] FormatStyle の作り方
[Swift] ParseableFormatStyle の作り方
Sponsor Link
環境&対象
- macOS Ventura 13.2.1
- Xcode 14.3 Beta
- iOS 16.0
これまでの記事
前回の記事では、FormatStyle の使い方について説明しました。
・FormatStyle を使用すると データ を 適切な表現の String に変換できる
[Swift] FormatStyle の使い方
・ParseableFormatStyle を使用すると適切な表現の String をデータに変換できる
[Swift] ParseableFormatStyle の使い方
FormatStyle を作る
よくある例ですが、PhoneNumber というデータを作って、そのデータをさまざまな表記で表示できるようにしてみます。
電話番号
対象とする電話番号について少し確認しておきます。
東京のような市外局番が 2桁 で始まる電話番号は、 XX – XXXX – XXXX であり、
神奈川/横浜のような市外局番が3桁で始まる電話番号は、XXX – XXX – XXXX となります。
つまり 電話番号は、2桁 – 4桁 – 4桁 フォーマットか、3桁 – 3桁 – 4桁 フォーマットかのどちらかのようです。そして、いずれにしても、10桁 です。
電話番号Q&A / 総務省のページは、こちら。
※ 携帯電話の番号は 3桁 – 4桁 – 4桁 で合計 11桁で、少し異なります。携帯番号も対応するのであれば、すこし手を入れる必要があります。
番号を3つに区切られますが、それぞれを 市外局番(areaCode)、市内局番(localCode)、番号(number) と呼ぶことにします。
実現したい表記
以下のフォーマットを使い分けたいとします。(市外局番3桁、市内局番3桁で例示してます)
・(XXX)XXX-XXXX (areaParenthesis)
・XXX(XXX)XXXX (localParenthesis)
・XXX-XXX-XXXX (hyphen)
カッコ内は、それぞれの表記名称です。
準備
FormatStyle を作るための準備として、最初に、PhoneNumber を定義します。
struct PhoneNumber
PhoneNumber は、電話番号データを持つための struct です。
public struct PhoneNumber: Equatable {
let areaCode: String // 2-3 digits
let localCode: String // 3-4 digits
let number: String // should be 4 digits
// note: acceptable format 2-4-4 or 3-3-4
}
順番に、市外局番・市内局番・番号 を”文字列” で定義しています。
FormatStyle 定義
PhoneNumber 向けの FormatStyle を定義していきます。
TDD で進めていきたいので、テストコードを書いてから実装をします。
FormatStyle は、実際に使用されるときは、データ型.formatted(…) のような形で使用されますが、内部実装としては formatStyle.format(データ型) というメソッドが使われていますので、そちらのメソッドのテストから書いていきます。
テストコード .format
ということで、まずは、.format のテストを書きます。
func test_FormatStyle() async throws {
let phone = PhoneNumber(areaCode: "045", localCode: "123", number: "4567")
XCTAssertEqual(PhoneNumberFormatStyle(.hyphen).format(phone), "045-123-4567")
XCTAssertEqual(PhoneNumberFormatStyle(.areaParenthesis).format(phone), "(045)123-4567")
XCTAssertEqual(PhoneNumberFormatStyle(.localParenthesis).format(phone), "045(123)4567")
}
3種類の表記をテストしています。
PhoneNumberFormatStyle実装
テストが書けたので実装していきます。
extension PhoneNumber {
public struct PhoneNumberFormatStyle : FormatStyle {
// (1)
public typealias FormatInput = PhoneNumber
public typealias FormatOutput = String
// (2)
public enum PhoneNumberStyle: Codable {
case areaParenthesis // (XX)XXXX-XXXX
case localParenthesis // XX(XXXX)XXXX
case hyphen // XX-XXXX-XXXX
}
var style: PhoneNumberStyle
init(_ style: PhoneNumberStyle = .hyphen) {
self.style = style
}
// (3)
public func format(_ phoneNumber: PhoneNumber) -> String {
switch style {
case .areaParenthesis:
return "(" + phoneNumber.areaCode + ")" + phoneNumber.localCode + "-" + phoneNumber.number
case .localParenthesis:
return phoneNumber.areaCode + "(" + phoneNumber.localCode + ")" + phoneNumber.number
case .hyphen:
return phoneNumber.areaCode + "-" + phoneNumber.localCode + "-" + phoneNumber.number
}
}
}
}
- FormatStyle の Input は、PhoneNumber で Output は String と定義します
- 実際のスタイルを enum で定義しています
- format メソッドの実装です
上記のコードでテストが通りますので、基本的な実装はできたことがわかります。
# デフォルトをハイフン表記にしていますが、深い意味はありません。
テストコード .formatted
実際に使用する時には、データ型.formatted として使いたいので、そのような使い方のテストも書いていきます。
func test_Formatted() async throws {
let phone = PhoneNumber(areaCode: "045", localCode: "123", number: "4567")
XCTAssertEqual(phone.formatted(.phone()), "045-123-4567")
XCTAssertEqual(phone.formatted(.phone(.areaParenthesis)), "(045)123-4567")
XCTAssertEqual(phone.formatted(.phone(.localParenthesis)), "045(123)4567")
}
ここまでくると、Foundation で定義されている他の FormatStyle と見劣りしなくなります。
PhoneNumber の extension を実装
先ほどのテストをパスさせるために、PhoneNumber の extension を実装していきます。
extension PhoneNumber {
public func formatted(_ format: S) -> S.FormatOutput where S: FormatStyle, S.FormatInput == PhoneNumber {
return format.format(self)
}
}
上記で、FormatInput が PhoneNumber である FormatStyle を使って、PhoneNumber を フォーマットすることができるようになります。
機能的には十分かもしれませんが、ここまでの定義だけだと、以下のような書き方が必要になってしまいます。
phone.formatted(PhoneNumber.PhoneNumberFormatStyle(.localParenthesis))
使えないこともないですが、FormatStyle に static で代表的なフォーマットを定義しておくのが、便利です。
extension FormatStyle where Self == PhoneNumber.PhoneNumberFormatStyle {
static func phone(_ style: PhoneNumber.PhoneNumberFormatStyle.PhoneNumberStyle = .hyphen) -> PhoneNumber.PhoneNumberFormatStyle { .init(style) }
}
上記のように定義しておくことで、以下のような記述が可能になります。
number.formatted(.phone(.localParenthesis))
ここまで実装すると、先ほどのテストもパスするようになります。
まとめ
独自型に対して、FormatStyle の作り方を説明しました。
- FormatStyle に conform させる
- FormatInput = 独自型、FormatOutput = String とする
- format メソッドを定義して、フォーマットした出力を行う
- データ型の extension で .formatted を定義するのが Swift 流
- FormatStyle にも extension で static を定義しておくと便利
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
SwiftUI おすすめ本
SwiftUI を理解するには、以下の本がおすすめです。
SwiftUI ViewMatery
SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。
英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。
View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。
超便利です
販売元のページは、こちらです。
SwiftUI 徹底入門
# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。
Swift学習におすすめの本
詳解Swift
Swift の学習には、詳解 Swift という書籍が、おすすめです。
著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。
最新版を購入するのがおすすめです。
現時点では、上記の Swift 5 に対応した第5版が最新版です。
Sponsor Link