Open5

SwiftUI: sheetだとiOSの仮想キーボードに「閉じる」が付かない件

kabeyakabeya

iOSでテキストフィールドにフォーカスが当たったとき、仮想?スクリーン?キーボードが表示されます。これには「閉じる」ボタンがついてないので、もし閉じる必要がある場合、自分で閉じる処理を書くか、「閉じる」ボタンを追加してやる必要があります。

「閉じる」ボタンを追加するのはおよそ以下のような感じになります。
自分のビューのテキストフィールドにフォーカスが当たっている場合のみ、キーボードの上に閉じるボタンを表示します。
(「(自分のビューのテキストフィールドに)フォーカスが当たっている場合のみ」の判定を入れなくてもこのケースではうまく行きます。ですが、色々と部品化していくと、他のカスタムビューのテキストフィールドでも同じ処理を書いていて、複数のカスタムビューを組み合わせた際に「閉じる」ボタンがいくつも表示されてしまうという現象が発生します。これはそれを避けるための判定になります)

struct NameView: View {
    @State var name: String = ""
    @FocusState private var isFocused: Bool
    
    var body: some View {
        VStack {
           TextField("名前", text: $name)
                .focused($isFocused)
                .toolbar {
                    if isFocused {
                        ToolbarItemGroup(placement: .keyboard) {
                            Spacer()
                            Button("閉じる") {
                                self.isFocused = false
                            }
                        }
                    }
                }
        }
    }
}

ですが、これが効くのはNavigationView/NavigationStackの中にある場合のみのようです。
.sheetから上記のNameViewを表示した場合、これだけだとキーボードの上に閉じるボタンが付きません。

なので、もし.sheetで表示する場合は、

    .sheet(isPresented: $isSheetVisible) {
       NameView()
    }

ではダメで、

    .sheet(isPresented: $isSheetVisible) {
         NavigationStack {
               NameView()
         }
    }

のようにNavigationViewNavigationStackで囲んでやる必要があります。
NameViewが他のNavigationStackからも遷移してくるケースも考慮する場合の話。そうでないなら、NameViewbody直下にNavigationStackを入れてやればよいと思います)

kabeyakabeya

「閉じる」がつかなくなるというか、「閉じる」がつくはずの画面なのについてなくて、他の画面とかを色々触っているうちにいつの間にか「閉じる」がつくようになっている、というケースがあります。

なにか条件があるような気もするのですが、なんなんでしょうか。

kabeyakabeya

ですが、これが効くのはNavigationView/NavigationStackの中にある場合のみのようです。

というわけではないようです。
NavigationView/NavigationStackの中でなくとも効くようです。

ということは?

  • シートでない場合は、NavigationView/NavigationStackの中にあるかどうかに依らず効く。
  • シートの場合は、NavigationView/NavigationStackの中にないと効かない。

ということですかね。

kabeyakabeya

ということは?

  • シートでない場合は、NavigationView/NavigationStackの中にあるかどうかに依らず効く。
  • シートの場合は、NavigationView/NavigationStackの中にないと効かない。
    ということですかね。

Appleの.toolbarのドキュメントにはNavigationStackの文字はないので、こうではないように見えます。

https://developer.apple.com/documentation/swiftui/view/toolbar(content:)-7vdkx

色々試した結果、NavigationStackがあると最初から表示されるけども、NavigationStackがないと最初からは表示されず、アプリを中断して再度戻ってきたときなど、何かをきっかけにして表示されるようになるケースがある、ということが分かりました。

なんとなく、スクリーンキーボード(のツールバー)の初期化と、ビュー本体の初期化が別々に行われているのに、.toolbarはビューの宣言に書かれているので、うまく同期できてないんじゃないかという気がします。

NavigationStackを入れると何かもうちょっと複雑なことが行われてどういう訳か同期する。

この.toolbar { ToolbarItem(placement: .keyboard) }はまだ癖がありそうです。