🔀
[SwiftUI] データバインド時に型の変換を可能にする
一般的な名称を知らないのですが、データバインドの際に型変換するのが厄介で困ります。
一番わかりやすい例はTextField
で数値を入力する場面です。
struct ContentView: View {
@State private var number = 0
var body: some View {
//できない!
TextField("数値を入力", text: $number)
}
}
この場面ではFormatter
を用いる選択肢がありますが、一般的には使えません。
その他にもColorPicker
でUIColor
を、というように、型変換が必要な場面は往々にしてあるものです。
以前この記事で書いた方法はsubscript
を用いるものですが、型変換だけならばもう少し楽にかけてもいいんじゃないかと思います。そこでBinding
にメソッドを追加することでこれを実現してみましょう。
converted
メソッド
以下のような関数をextension
で追加します。
public extension Binding {
func converted<T>(forward forwardConverter: @escaping (Value) -> T, backward backwardConverter: @escaping (T) -> Value) -> Binding<T> {
.init(
get: {
return forwardConverter(self.wrappedValue)
},
set: {newValue in
self.wrappedValue = backwardConverter(newValue)
}
)
}
}
これがどう使えるのでしょうか。上のTextField
の例でやってみましょう。
struct ContentView: View {
@State private var number = 0
var body: some View {
TextField("数値を入力", text: $number.converted(forward: {String($0)}, backward: {Int($0) ?? 0}))
}
}
forward
に指定したのはInt
を受け取ってString
を返す関数、backward
に指定したのはString
を受け取ってInt
を返す関数です。これだけで型変換が可能になります。
あるいは以下のように書いてもいいでしょう。スッキリしていて良さそうです。
struct ContentView: View {
@State private var number = 0
var body: some View {
TextField("数値を入力", text: $number.converted(forward: toString, backward: toInt))
}
private func toString(value: Int) -> String {
return String(value)
}
private func toInt(value: String) -> Int {
return Int(value) ?? 0
}
}
Color
とUIColor
のように相互の変換が失敗不可能なイニシャライザで行えるなら、記述はさらにシンプルになります。
struct ContentView: View {
@State private var color = UIColor.black
var body: some View {
ColorPicker("色を選択", selection: $color.converted(forward: Color.init, backward: UIColor.init))
}
}
型変換に限ってはこの方式が一番綺麗じゃないかな、と考えています。
Discussion
Bindingのイニシャライザ
init(get:set:)
を使う方法もありますよ。クロージャで読み書きの処理を指定できるし、コード量も少ないのでかなり応用が効くと思います。ありがとうございます!
わかりにくかったかもしれないのですが、この記事の
converted
を用いた方法でも内部ではBinding
のイニシャライザを利用しています。型変換を行うのはそこそこ一般的な要求なので、View
の側で明示的にBinding
を定義せずに済ませたいというのがモチベーションでした。もうちょっとよく読むべきでした、すいません😅
型変換をしっかり関数で書けるので、型変換の処理が複雑な場合などは記事で紹介されている方がわかりやすいですね。
いえいえ!