1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【SwiftUI】キーボードの表示によってコンテンツが隠れないようにする方法

Posted at

SwiftUIでキーボードによる入力が必要になる画面では、キーボードによってコンテンツが隠れてしまわないように気を遣うと思います。
本記事では、キーボードによってコンテンツが隠れないようにする方法を紹介します。

デフォルトの挙動

SwiftUIはデフォルトでは、キーボードが表示されるとその分コンテンツも上に上がり、キーボードに被らないような挙動になります。

struct SampleView: View {
    
    @State var text = ""
    
    var body: some View {
        VStack {
            ForEach(1...20, id: \.self) {
                Text("value: \($0)")
            }
            Spacer()
            TextField("Text", text: $text)
                .textFieldStyle(.roundedBorder)
                .padding(.bottom, 16)
        }
        .padding(.horizontal, 16)
    }
}

Simulator Screen Recording - iPhone 16 Pro - 2025-08-23 at 13.05.52.gif

上記のように、テキストフィールドをフォーカスしてキーボードが表示されると、それに合わせて隠れるはずのテキストフィールドもにゅっと見える位置まで表示されます。
そのため、何も考えていなくてもキーボードによってコンテンツが隠れてしまうという挙動を回避できていることもあります。

デフォルトの挙動だとコンテンツが隠れてしまうケース

ただ、デフォルトの挙動でもコンテンツが隠れてしまうケースもあります。

struct SampleView: View {
    
    @State var text = ""
    
    var body: some View {
        VStack {
            ForEach(1...20, id: \.self) {
                Text("value: \($0)")
            }
            Spacer()
            TextField("Text", text: $text)
                .textFieldStyle(.roundedBorder)
                .padding(.bottom, 16)
        }
+       .ignoresSafeArea()
        .padding(.horizontal, 16)
    }
}

Simulator Screen Recording - iPhone 16 Pro - 2025-08-23 at 13.13.35.gif

セーフエリアを無視して画面全体に表示したいという時に使う.ignoresSafeArea()をつけると、キーボードが表示されてもテキストフィールドは見える位置まで移動してくれません。

対処法

この対処法として、ignoresSafeAreaモディファイアの第一引数に.containerと指定してやれば良いです。

struct SampleView: View {
    
    @State var text = ""
    
    var body: some View {
        VStack {
            ForEach(1...20, id: \.self) {
                Text("value: \($0)")
            }
            Spacer()
            TextField("Text", text: $text)
                .textFieldStyle(.roundedBorder)
                .padding(.bottom, 16)
        }
-       .ignoresSafeArea()
+       .ignoresSafeArea(.container)
        .padding(.horizontal, 16)
    }
}

Simulator Screen Recording - iPhone 16 Pro - 2025-08-23 at 13.16.17.gif

ignoresSafeAreaモディファイアで無視できるセーフエリアには以下2種類あり、ignoresSafeAreaに何も引数を指定しない場合は、以下両方の領域を無視することになります。

  • container: 上部のバーや下部のバーなどの要素を含む領域(たぶん一般的に語られるセーフエリアの領域)
  • keyboard: キーボードの領域

そのため、.ignoresSafeArea(.container)としてkeyboardの領域は無視しないよと明示的に指定してやることで、従来通りキーボードの領域を認識してテキストフィールドが隠れないようになります。

テキストフィールド以外のコンテンツもキーボードに隠れないようにしたい

デフォルトの挙動では、キーボードの入力先の要素がキーボードに隠れないようにしてくれるだけです。
例えば、キーボードの下にあるボタンをキーボードに隠れないようにしたい場合は、デフォルトの挙動では物足りません。

struct SampleView: View {
    
    @State var text = ""
    
    var body: some View {
        ScrollView {
            VStack {
                ForEach(1...100, id: \.self) {
                    Text("value: \($0)")
                }
                Spacer()
                TextField("Text", text: $text)
                    .textFieldStyle(.roundedBorder)
                    .padding(.bottom, 16)
                Spacer()
                    .frame(height: 50)
                Button("Button") {
                    print("tapped button")
                }
                .buttonStyle(.bordered)
                .background(.red)
            }
        }
        .padding(.horizontal, 16)
    }
}

Simulator Screen Recording - iPhone 16 Pro - 2025-08-23 at 13.39.21.gif

対処法

上記の例で、キーボード表示時にボタンまで隠れないようにするのは、ScrollViewReaderを使ってキーボード表示後にボタンが見える位置までスクロールすることで実現できます。

この方法は以下記事を参考にさせていただきました!

struct SampleView: View {
    
    @State var text = ""
    private static let buttonId = "buttonId"
    
    var body: some View {
        ScrollViewReader { proxy in
            ScrollView {
                VStack {
                    ForEach(1...100, id: \.self) {
                        Text("value: \($0)")
                    }
                    Spacer()
                    TextField("Text", text: $text)
                        .textFieldStyle(.roundedBorder)
                        .padding(.bottom, 16)
                        .ignoresSafeArea(.keyboard)
                    Spacer()
                        .frame(height: 50)
                    Button("Button") {
                        print("tapped button")
                    }
                    .buttonStyle(.bordered)
                    .background(.red)
                    .id(Self.buttonId)
                }
            }
            .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { _ in
                Task {
                    try await Task.sleep(for: .seconds(0.1))
                    withAnimation {
                        proxy.scrollTo(Self.buttonId, anchor: .bottom)
                    }
                }
            }
        }
        .padding(.horizontal, 16)
    }
}

Simulator Screen Recording - iPhone 16 Pro - 2025-08-23 at 14.08.01.gif

ポイントとして、まず以下コードで、キーボードの表示のタイミングを検知し、ScrollViewReaderから取得したScrollViewProxyで任意の位置までスクロールを行います。
また、キーボードを表示を検知してすぐだとスクロール処理がうまく機能しない場合があったので、0.1秒の遅延処理を入れています。
おそらく、デフォルトでテキストフィールドを表示しようとする挙動と、衝突してうまくいかないのかなと思っていますが、この辺り詳しい方いたら教えて欲しいです🙏

.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { _ in
    Task {
        try await Task.sleep(for: .seconds(0.1))
        withAnimation {
            proxy.scrollTo(Self.buttonId, anchor: .bottom)
        }
    }
}

onReceiveで監視するイベントをkeyboardWillShowNotificationにしてしまうと、キーボード表示前でキーボード分のセーフエリアが追加されていないので、さらに遅延処理が必要になるかもです。

おわり

以上が、私が知っているキーボードの表示によってコンテンツが隠れるのを防ぐ方法でした!
他にも良い方法があれば、共有してもらえると嬉しいです!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?