🐕
(SwiftUI)Binding<t?>をBinding<t>に変換する
はじめに
以下の環境で動作確認しました。
- Xcode 13.2.1 (13C100)
- iOS 15.1
Binding<t?>
をBinding<t>
に変換する
(問題)以下のコードについて考えます。
struct ContentView: View {
@State var sizes = [Int: CGFloat]()
// ...省略...
}
さて、self.sizes[index]
として要素を取り出すと、その型はBinding<CGFloat?>
になります。Binding<CGFloat>
に変換するにはどうすれば良いでしょうか?
Optional型から具体的な型を取り出す方法は3種類あります。
- オプショナルバインディング
-
??
演算子を利用してデフォルト値を設定する -
!
演算子を利用して強制的にunwrapする
しかし、これらの方法はt?
をt
に変換する方法であり、Binding<t?>
に対しては利用できません。
(解決策)その場でBinding型の値を作る
Binding<t?>
からBinding<t>
に変換することはできません。そこで、Binding型の値を作ることで対応します。
Binding型にはイニシャライザとしてinit(get: () -> Value, set: (Value) -> Void)
が定義されています。今回はこれを利用します。
Binding(get: { self.sizes[index] ?? デフォルト値 }, set: { self.sizes[index] = $0 })
このように実装することでBinding<CGFloat?>
からBinding<CGFloat>
に変換できます。
※上記は解決策のひとつです。もっとシンプルな解決策をご存知の場合はコメントいただけると助かります。
(おまけ)@Bindingに割り当てる
1秒ごとに「Hello, World!」の文字の大きさがランダムに変化するアプリを作ってみました。Xcodeを起動して新規プロジェクトを作成したら、ContentView.swiftに以下のコードを貼り付けてください。
import SwiftUI
struct HelloWorld: View {
@Binding var size: CGFloat
var body: some View {
Text("Hello, World!")
.font(.system(size: self.size))
}
init(size: Binding<CGFloat>) {
self._size = size
}
}
struct ContentView: View {
@State var sizes = [Int: CGFloat]()
var body: some View {
VStack {
ForEach(0..<5) { index in
HelloWorld(
size: Binding(
get: { self.sizes[index] ?? 16 }, set: { self.sizes[index] = $0 }))
}
}
.onAppear {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
let i = Int.random(in: 0..<5)
let size = Int.random(in: 16...64)
self.sizes[i] = CGFloat(size)
}
}
}
}
実行すると4行の「Hello, World!」が表示されます。そして、1秒ごとにそれぞれの文字の大きさが16から64の範囲でランダムに変化します。
Discussion