【Swift UI】TextFieldのキーボードを閉じる方法と@FocusStateの使い方
この記事からわかること
- SwiftUIでTextFieldでnumberPadを指定する場合
- キーボードを閉じることができない場合の解決法
- @FocusStateの使い方と閉じるボタンの自作方法
- 子ViewにあるTextFieldの閉じるボタンを実装する方法
- ビルドしたシミュレーターにキーボードを出現させる
- 閉じるボタンが表示されない問題の解決法
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
Swift UIでアプリを開発中にTextField
の入力画面でキーボードをnumberPad
(数字のみ)にしたところ閉じることができなくなってしまいました。
また親Viewと子Viewに分けて作成していたところ、子ViewのTextField
に閉じるボタンを実装しようとしても表示されませんでした。
今回は「閉じるボタンの実装方法」と「子ViewにあるTextFieldへの閉じるボタンの実装方法」をまとめていきます。
TextFieldでnumberPadのみ発生する閉じれない問題
Swift UIではTextField
を使うと簡単に以下のような入力可能な要素を作成できます。
TextField("km", text: $milage)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
入力欄にフォーカスを当てるとキーボードが表示されますが、数字のみの入力にしたい場合は.keyboardType(.numberPad)
で数字キーのみに指定することができました。
しかし通常はEnterキー
の押下でキーボードは閉じられますが、数字キーのみの場合はEnterキー
がなく閉じることができなくなってしまいます。
解決策として閉じるボタンを自作で実装することでキーボードを閉じることができるようにしていきます。
閉じるボタンを自作方法
閉じるボタンを自作する際に重要なポイント
- フォーカスを操作できるようにする
- @FocusState
- toolbarとToolbarItemGroup
TextField
のキーボードが出現するのは対象のTextField
にフォーカスが当たっている時です。TextField
のフォーカスは.focused(変数)
で取得、操作することが可能になっています。(Bool値
)
TextField("km", text: $milage)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
.focused($isActive) // このTextFieldのフォーカスをBool値で取得、操作
@FocusStateの使い方
@FocusState var isActive:Bool
.focused(変数)
に渡す変数は@FocusStateが付与されていないといけません。
@FocusState
とはiOS15以降(Swift5.1)から使えるようになったProperty Wrapper(プロパティラッパー)の1つです。プロパティラッパーはプロパティに対する操作や処理をカプセル化して定義することで特定のキーワードを付与するだけで任意の動作や挙動を行わせることができる便利な機能です。
その中でも@FocusState
はその名前の通り、TextFieldなどの要素のフォーカス制御を可能にしています。
@FocusStateを付与したコード
struct ContentView: View {
@State var milage:String = ""
@FocusState var isActive:Bool
var body: some View {
TextField("km", text: $milage)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
.focused($isActive)
}
}
しかしこれではまだボタンの実装はできていません。実際のボタンは.toolbar
で実装していきます。
toolbarとToolbarItemGroup
ボタンを実装するにはtoolbar
モディファイアのToolbarItemGroup
内に実装していきます。
閉じるボタンを実装したコード
struct ContentView: View {
@State var milage:String = ""
@FocusState var isActive:Bool
var body: some View {
TextField("km", text: $milage)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
.focused($isActive)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer() // 右寄せにする
Button("閉じる") {
isActive = false // フォーカスを外す
}
}
}
}
}
.focused
をつけてフォーカスを制御できるようにしたTextField
にtoolbar
モディファイアを追加します。中にはToolbarItemGroup
を定義し、引数のplacement
は.keyboard
を指定します。
ToolbarItemGroup
の中にはButton
を定義し、そのアクション部分にフォーカスが外れる処理(falseを格納)します。
Button
の上側にSpacer()
を入れることで閉じるボタンを右寄せにすることができます。
これで数字のみのキーボードに閉じるボタンを実装することができました。
Stackの中に複数のTextFieldがある場合
TextField
が複数あり、○Stack
で囲んでいる場合はtoolbar
モディファイアは○Stack
に指定してもOKです。
struct ContentView: View {
@State var milage:String = ""
@State var refueling:String = ""
@FocusState var isActive:Bool
var body: some View {
VStack {
HStack {
Text("走行距離")
TextField("km", text: $milage)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
.focused($isActive)
}
HStack{
Text("給油量")
TextField("ℓ",text: $refueling)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
.focused($isActive)
}
}.toolbar { // VStackに指定
ToolbarItemGroup(placement: .keyboard) {
Spacer() // 右寄せにする
Button("閉じる") {
isActive = false // フォーカスを外す
}
}
}
}
}
フォーカスを制御している変数はTextField
が複数あっても1つで問題ありませんでした。toolbar
モディファイアを特定の1つのTextField
に指定しても期待通りの動作をしましたが、可読性の観点から囲んでいるStackに当てるのがおすすめです。
子ViewにあるTextFieldへの閉じるボタンの実装方法
解決策
親Viewにfocusedとtoolbarを実装する
先ほどのように1つのビュー内で入れ子になっている場合はその一番上の要素の当てるだけでよかったですが、親ビューから子ビューを呼び出している場合は閉じるボタンの実装に注意が必要です。
子ビュー側に@FocusState
と「閉じるボタン」を実装して親ビューから呼び出してみたところ数字のみのキーパッドに閉じるボタンどころかツールバー自体が表示されませんでした。
閉じるボタンが表示されなかったコード
子ビュー
struct TextFieldTest: View {
@State var milage:String = ""
@FocusState var isInputActive:Bool // ナンバーパッドのフォーカス
var body: some View {
TextField("km", text: $milage)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
.focused($isInputActive)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer() // 右寄せにする
Button("Done") {
isInputActive = false
}
}
}
}
}
親ビュー
struct ContentView: View {
@State var selectedTag = 1
var body: some View {
TabView(selection: $selectedTag){
TextFieldTest().tabItem{
Text("Field1")
}.tag(1)
TextFieldTest().tabItem{
Text("Field2")
}.tag(2)
}
}
}
これを解決するためには親ビュー側にフォーカスの制御(@FocusState
)とツールバーへの閉じるボタンを追加を記述しないといけないようです。
解決したコード
子ビュー
struct TextFieldTest: View {
@State var milage:String = ""
var body: some View {
TextField("km", text: $milage)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numberPad)
}
}
親ビュー
struct ContentView: View {
@State var selectedTag = 1 // タブ切り替え
@FocusState var isInputActive:Bool // ナンバーパッドのフォーカス
var body: some View {
TabView(selection: $selectedTag){
TextFieldTest().tabItem{
Text("Field1")
}.tag(1)
.focused($isInputActive) // フォーカスの制御
TextFieldTest().tabItem{
Text("Field2")
}.tag(2)
.focused($isInputActive) // フォーカスの制御
}
.toolbar { // ツールバーを親の一番上の要素に実装
ToolbarItemGroup(placement: .keyboard) {
Spacer() // 右寄せにする
Button("Done") {
isInputActive = false
}
}
}
}
}
ビルドしたシミュレーターにキーボードを出現させる
プレビューではキーボードが表示されないのでシミュレーターを起動させて確認する必要があります。
ツールバーにある ボタンをクリックしてシミュレーターを起動させてTextField
にフォーカスを当ててもキーボードが出ない場合もあります。
解決するには「Simulator」>「I/O」>「Keyboard」>「Toggle Software Keyboard」をクリックするとフォーカスが当たった時にキーボードが出現するようになります。
TextFieldの使い方
理解を深めるためにTextField
の使い方と注意点をまとめておきます。
TextFieldを使用するポイント
- 入力値はバインディングされた変数に格納される
- 格納される値はString型になる
- 引数:titleKeyには入力内の文字を指定
- textFieldStyleモディファイアで見た目を変更可能
- keyboardTypeモディファイアで表示されるキーボードを指定可能
構文
TextField(titleKey: LocalizedStringKey, text: Binding<String>)
使用例
TextField("km", text: $milage)
.textFieldStyle(RoundedBorderTextFieldStyle()) // 角に丸みを帯びた入力要素
.keyboardType(.numberPad) // 数字のみのキーボードを表示
カスタマイズした枠線をつける方法
TextFieldに枠線を付けるには先ほど紹介したtextFieldStyle(RoundedBorderTextFieldStyle())
を指定することで「角に丸みを帯びた枠線」を付与することができます。
しかし色をつけた枠線を付ける方法は今のところないので要素の上に要素を重ねることのできるoverlay
モディファイアを使用して枠線を作成していきます。
TextField("住所", text: $address)
.overlay(
RoundedRectangle(cornerRadius: 2)
.stroke(.cyan,lineWidth: 3)
)
閉じるボタンが表示されない問題の解決法
以下のようにsheet
モディファイアを使用して表示させたビューではなぜか「閉じるボタン」が表示されませんでした。それだけでなくナビゲーションタイトルも表示されませんでした。sheet
モディファイアを使用したビューではナビゲーションバー(ツールバー)が表示されないようです。
struct TestNavigationView: View {
@FocusState var isInputActive: Bool
@State var text: String = ""
@State var isModal: Bool = false
var body: some View {
NavigationStack {
Button {
isModal = true
} label: {
Text("isModal")
}
.sheet(isPresented: $isModal, content: {
VStack {
TextField("", text: $text)
.frame(width: 120)
.textFieldStyle(RoundedBorderTextFieldStyle())
.focused($isInputActive)
}.navigationBarTitle("Test")
.toolbar {
ToolbarItem(placement: .keyboard) {
Button("閉じる") {
isModal = false
}
}
}
})
}
}
}
これを解決するには以下のようにsheet
モディファイアで渡すビューもNavigationStack
で囲むことで正常に表示されるようになりました。(この方法が正しいのかは分かりませんが...)
解決策(NavigationStackで囲むだけ)
struct TestNavigationView: View {
@FocusState var isInputActive: Bool
@State var text: String = ""
@State var isModal: Bool = false
var body: some View {
NavigationStack {
Button {
isModal = true
} label: {
Text("isModal")
}
.sheet(isPresented: $isModal, content: {
NavigationStack {
VStack {
TextField("", text: $text)
.frame(width: 120)
.textFieldStyle(RoundedBorderTextFieldStyle())
.focused($isInputActive)
}.navigationBarTitle("Test")
.toolbar {
ToolbarItem(placement: .keyboard) {
Button("閉じる") {
isModal = false
}
}
}
}
})
}
}
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。