概要
SwiftUI には ignoresSafeArea という modifier がありますが、しっかり理解せず使っていたので基本的な動作や引数の役割、SwiftUI の自動でのキーボード避けとの関係について調べてまとめました。
ignoresSafeArea とは
SwiftUI の View はデフォルトでは SafeArea の中に配置されます。例えば画面全体に背景色を設定しようとすると、背景色は以下のように SafeArea 内だけに適用されます。
struct ContentView: View {
var body: some View {
Color.yellow
}
}
ときにはこの振る舞いが余計なお世話になってしまうこともあるでしょう。たとえば、上の画面では背景色の黄色が SafeArea 外にも適用されている方が自然な感じがします。このようなときに ignoresSafeArea が使えます。今回の Color のように可能な限り広い領域を占有しようとする性質を持つ View に ignoresSafeArea が適用されると SafeArea の制限を無視して表示されるようになります。
struct ContentView: View {
var body: some View {
Color.yellow
.ignoresSafeArea()
}
}
引数を何も指定しないと全ての SafeArea を無視しますが、edges パラメータを指定することで特定の方向の SafeArea だけ無視することも可能です。たとえば、以下のようにすると画面下側の SafeArea だけ無視するようにできます。
struct ContentView: View {
var body: some View {
Color.yellow
.ignoresSafeArea(edges: [.bottom])
}
}
SwiftUI のキーボード避け
ここで、急に画面にテキストフィールドを追加したくなったとしましょう。以下のようなコードになりそうです。
struct ContentView: View {
@State private var inputText: String = ""
var body: some View {
ZStack {
Color.yellow
VStack {
Spacer()
TextField("急に追加されたテキストフィールド", text: $inputText)
.padding()
.background(Color.white)
.padding(.bottom, 100)
}
}
.ignoresSafeArea(edges: [.bottom])
}
}
しかし、テキストフィールドに実際に入力しようとすると上からキーボードが被さってきてしまいます。
以前の iOS 開発であれば画面がキーボードを避ける実装を自前でしなければいけないのが当たり前でしたが、iOS 14 から SwiftUI を使っていれば自動でキーボード避けをしてくれるはずです。それなのになぜ上の実装ではキーボードを避けてくれないのでしょうか。
そもそも SwiftUI のキーボード避けはどのように実現されているかを整理します。実際の実装方法は想像するしかありませんが、自分は
- キーボードが SafeArea 外として扱われる
- SwiftUI の View は SafeArea の内部
という2つが組み合わさって View がキーボードを避けてくれるのだと理解しています(間違っているぞ!という方はコメントください)。キーボードが表示されると SafeArea の高さが増えることは、以下のように GeometryReader を追加することで確認できます。
struct ContentView: View {
@State private var inputText: String = ""
var body: some View {
GeometryReader { geometry in
ZStack {
Color.yellow
Text("bottom safe area: \(geometry.safeAreaInsets.bottom)")
VStack {
Spacer()
TextField("急に追加されたテキストフィールド", text: $inputText)
.padding()
.background(Color.white)
.padding(.bottom, 100)
}
}
.ignoresSafeArea(edges: [.bottom])
}
}
}
上記のテキストフィールドの例では、.ignoresSafeArea(edges: .bottom) で View 全体に画面下部の SafeArea を無視するように指示しているので、まさに画面下部の SafeArea であるキーボードを避けてくれなくなったということになります。
ignoresSafeArea の第一引数: SafeAreaRegions
それでは SafeArea の外側まで View を広げたいけどキーボードは避けてほしいというときはどうすればいいのでしょうか。冷静に ignoresSafeArea のシグネチャを見ると第一引数に SafeAreaRegions を指定するようになっています。
その SafeAreaRegions のドキュメントを見ると、 SafeArea の領域を表すものであり、以下の3つの static プロパティを持つことがわかります。
-
.container: デバイスの種類などによって決まる画面上部や下部の領域のこと。我々が普段 SafeArea として意識するのはこちら -
.keyboard: View に被さってくるソフトウェアキーボードの領域のこと .all
.all には .container と .keyboard の両方が含まれることになります。ignoresSafeArea ではデフォルトで .all が設定されているのでいずれの SafeArea も無視することになるのですが、.container を指定するとキーボードによる SafeArea は無視しなくなる、すなわちキーボード避けの挙動を維持してくれるようになります。
struct ContentView: View {
@State private var inputText: String = ""
var body: some View {
ZStack {
Color.yellow
VStack {
Spacer()
TextField("急に追加されたテキストフィールド", text: $inputText)
.padding()
.background(Color.white)
.padding(.bottom, 100)
}
}
.ignoresSafeArea(.container, edges: [.bottom]) // .container を指定
}
}
あまり使うことはなさそうですが、逆に .ignoresSafeArea(.keyboard) と指定すれば View を SafeArea 内におさめつつ自動でのキーボード避けを無効化することができます。
まとめ
- SwiftUI の View はデフォルトでは SafeArea 内に配置されるが、
ignoresSafeAreaを使うとそれを無視して SafeArea 外に View をはみ出させることができる -
SafeAreaRegionsという構造体が SafeArea の領域を表し、デバイスによって決まる画面上部や下部の SafeArea 外の領域を表す.container、ソフトウェアキーボードを表す.keyboard、その両方を含む.allがある -
ignoresSafeAreaにはどのSafeAreaRegionsを無視するかを第一引数で渡せるが、デフォルト値は.allなので何も指定しないと.keyboardも無視されて自動でキーボードを避けてくれなくなる。この問題は明示的に.containerを指定すると解決する
WWDC21 Add rich graphics to your SwiftUI app
コメント で教えていただいたのですが、以下のセッションの冒頭で SafeArea について詳しく説明されているのでぜひ参考にしてみてください。この記事で書いている container / keyboard それぞれによる SafeArea についても触れられています。
参考
- https://developer.apple.com/documentation/swiftui/view/ignoressafearea(_:edges:)
- https://developer.apple.com/documentation/swiftui/safearearegions
- https://swiftwithmajid.com/2021/11/03/managing-safe-area-in-swiftui/
- https://www.fivestars.blog/articles/swiftui-keyboard/
- https://stackoverflow.com/questions/57116723/how-to-access-safe-area-size-in-swiftui
- https://www.hackingwithswift.com/quick-start/swiftui/how-to-place-content-outside-the-safe-area


