iTranslated by AI
[SwiftUI] Enabling type conversion during data binding
I don't know the general name for it, but it's troublesome when type conversion during data binding becomes cumbersome.
The most straightforward example is when inputting a numerical value using a TextField.
struct ContentView: View {
@State private var number = 0
var body: some View {
// Cannot do this!
TextField("Enter a number", text: $number)
}
}
While there is an option to use a Formatter in this scenario, it's not universally applicable.
There are many other situations where type conversion is needed, such as using UIColor with a ColorPicker.
The method I wrote about in the previous article used subscript, but if it's just for type conversion, I think it should be possible to write it a bit more easily. So, let's achieve this by adding a method to Binding.
converted Method
Add the following function via an 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)
}
)
}
}
How can this be used? Let's try it with the TextField example above.
struct ContentView: View {
@State private var number = 0
var body: some View {
TextField("Enter a number", text: $number.converted(forward: {String($0)}, backward: {Int($0) ?? 0}))
}
}
The function specified for forward takes an Int and returns a String, and the function for backward takes a String and returns an Int. This is all it takes to enable type conversion.
Alternatively, you could write it like this. It looks quite clean.
struct ContentView: View {
@State private var number = 0
var body: some View {
TextField("Enter a number", 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
}
}
If the conversion can be done using initializers that cannot fail, such as between Color and UIColor, the description becomes even simpler.
struct ContentView: View {
@State private var color = UIColor.black
var body: some View {
ColorPicker("Select a color", selection: $color.converted(forward: Color.init, backward: UIColor.init))
}
}
I believe this approach is the cleanest specifically for type conversion.
Discussion
Bindingのイニシャライザ
init(get:set:)を使う方法もありますよ。クロージャで読み書きの処理を指定できるし、コード量も少ないのでかなり応用が効くと思います。ありがとうございます!
わかりにくかったかもしれないのですが、この記事の
convertedを用いた方法でも内部ではBindingのイニシャライザを利用しています。型変換を行うのはそこそこ一般的な要求なので、Viewの側で明示的にBindingを定義せずに済ませたいというのがモチベーションでした。もうちょっとよく読むべきでした、すいません😅
型変換をしっかり関数で書けるので、型変換の処理が複雑な場合などは記事で紹介されている方がわかりやすいですね。
いえいえ!