SwiftUI.TextField に returnKeyType を設定するために Notification を活用する
概要
SwiftUI で TextField
の returnKeyType
(UIReturnKeyType
) を設定する標準的な API がなかったため、TextField
の裏側で使われている UITextField
を取り出して設定する方法の紹介です。
検証環境
- iOS 14.6
- Xcode 12.5
UITextField
を取り出す方法
よく見かける方法としては SwiftUI-Introspect のようなライブラリを使い、View 階層を辿って UITextField
を取り出す方法がありますが、この記事では Notification
を利用した方法を紹介します。
UITextField.textDidBeginEditingNotification
を利用する
UITextField.textDidBeginEditingNotification
が UIKit に定義されているので、それを使えば TextField
の入力開始時に、UITextField
を取得できます。
以下の方法だと、SwiftUI の裏側の View 構造を気にせずに UITextField
を取得できます。
struct ContentView: View {
@State var text = ""
var body: some View {
VStack {
TextField("Field 1", text: $text)
}
.textFieldStyle(RoundedBorderTextFieldStyle())
// 通知を受信する
.onReceive(
NotificationCenter.default.publisher(for: UITextField.textDidBeginEditingNotification),
perform: textDidBeginEditing
)
}
func textDidBeginEditing(_ notification: Notification) {
// UITextField を取り出せる
let textField = notification.object as! UITextField
// returnKeyType を設定できる
textField.returnKeyType = .done
}
}
ちゃんと done
になりました。
複数の TextField が存在する場合
上記の方法だけですと、複数の TextField
が存在する場合、全ての TextField
に適用されて困る場面があるので、何らかの方法で対象の TextField
かどうかを判断する必要があります。
onEditingChanged
と組み合わせる
ちょっと苦しい方法ですが、なかなか良い方法を思いつかなかったので、onEditingChanged
と組み合わせる方法を記載します。
単純ですが、TextField
の onEditingChanged
を使うことで、その入力欄の編集が開始されたか終了したかを取得できます。
これと、上記で紹介した Notification
を組み合わせることで、どの入力欄で編集している最中に Notification
が飛んできたかを判断することができます。
入力欄が二つあり、最後の入力欄のみ、returnKeyType
を done
にするには、以下のように記述します。
struct ContentView: View {
@State var text1 = ""
@State var text2 = ""
@State var focusedTextField: Int?
var body: some View {
VStack {
TextField(
"Field 1",
text: $text1,
onEditingChanged: { editing in
// どの入力欄かがわかる識別子を入れる
focusedTextField = editing ? 1 : nil
}
)
TextField(
"Field 2",
text: $text2,
onEditingChanged: { editing in
focusedTextField = editing ? 2 : nil
}
)
}
.onReceive(
NotificationCenter.default.publisher(for: UITextField.textDidBeginEditingNotification),
perform: textDidBeginEditing
)
}
func textDidBeginEditing(_ notification: Notification) {
let textField = notification.object as! UITextField
// 識別子を判定して設定を行う
if focusedTextField == 2 {
textField.returnKeyType = .done
}
}
}
まとめ
UITextField.textDidBeginEditingNotification
を利用することで、SwiftUI.TextField
の裏側に存在する UIKit.UITextField
を取得できました。
また、onEditingChanged
コールバックを利用することで、どの TextField
かを区別することもできました。
SwiftUI のみでうまくやる方法があれば良いですが、今回紹介した内容が何かの参考になれば幸いです。
追記
iOS 15 からは標準の submitLabel(_:)
が利用できるようになっています。
Discussion