🔖
.onChangeの代わりにBinding型のsetで任意の処理を実行する
概要
- 例えばSwiftUIの
TextField
で文字列を扱う際に、入力毎にアクションを挟みたいことがあります - これにはonChange(of:initial:_:)が使えますが、Viewの外側に
.onChange
を書くため実装のコードが別れてしまい少し見にくくなってしまう問題があります - そこで
Binding
型を返すextensionを定義して簡潔に書いてみます - 尚
onChange
のほうがパフォーマンスは良いそうなので、実際に利用する際はInstruments
でパフォーマンスを確認するとよさそうです
https://www.hackingwithswift.com/quick-start/swiftui/how-to-run-some-code-when-state-changes-using-onchange
That being said, please be sure to run your code through Instruments if you do this – using onChange() on a view is more performant than adding it to a binding.
実装
static関数を定義する方法
-
Binding
を作成するstatic関数を定義して、setの処理を引数として外から渡す方法です - 参考: https://github.com/hydro1337x/swiftui-clean-mvvm-c/blob/main/Presentation/Sources/Presentation/Extensions/Binding%2BExtension.swift
public extension Binding {
static func bind<T>(_ value: @autoclosure @escaping () -> T,
with action: @escaping (T) -> Void) -> Binding<T> {
Binding<T>(get: value, set: action)
}
}
- 用途としては下記のように、変更された値をStore側で管理したい場合に良さそうです
メソッドチェーンを使う方法
- もう一つはメソッドチェーンを使って
Binding型
から新しくBinding
型の変数を返すやり方です - 参考: How to run some code when state changes using onChange()
- こちらは通常のsetの処理のあとに、アクションを挟む形になっています
public extension Binding {
@MainActor
func onChange(_ handler: @escaping (Value) -> Void) -> Binding<Value> {
Binding(
get: { self.wrappedValue },
set: { newValue in
self.wrappedValue = newValue
handler(newValue)
}
)
}
}
利用例
- それぞれ下記のコードで動作確認できます
struct ContentView: View {
@State private var textA: String = ""
@State private var textB: String = ""
@State private var textC: String = ""
var body: some View {
Form {
TextField("Text A", text: .bind(textA, with: { textA=$0 ; print($0) }))
TextField("Text B", text: $textB.onChange({ print($0) }))
TextField("Text C", text: $textC)
.onChange(of: textC) { _, newValue in
print(newValue)
}
}
}
}
Discussion