The behaviors you described are all expected. We need to understand three things:
- SwiftUI layout system
- Keyboard safe area
- IgnoresSafeArea
(1) The most relevant concept in SwiftUI layout system is that views can have a fixed size or a flexible size. For example, a single-lined Text has a fixed size. So a VStack that only has Texts also has a fixed size. Try this
struct ContentView: View {
var body: some View {
VStack {
Text("Hello")
Text("World")
}
.border(Color.green)
}
}
You can see the green border only wraps around the texts.
On the other hand, Spacer
s, Color
s, GeometryReader
s, etc. have flexible sizes, they tend to expand to occupy all the space available.
(2) When the keyboard is showing, there is a safe area applied on the container. The height of the container will decrease.
(3) The ignoresSafeArea modifier is typically applied on views that have a flexible height. Taking the bottom edge for example, the modifier will have an effect only when the original bottom edge of the view is just aligned with the top edge of the bottom safe area or is covered by the bottom safe area. So if the view's bottom edge is far away from the top edge of the bottom safe area, ignoresSafeArea will have no effect.
Now I'll explain why all your examples have expected behavior.
Example 1
struct ContentView: View {
@State var text: String = ""
var body: some View {
TextField("Testing", text: $text)
}
}
Behavior: The text field moves up a little when the keyboard shows even if it's not covered by the keyboard.
Reason: The safe area is on the container, when the keyboard shows, the height of the container decreases. Since the text field is placed at the center of the container, it moves up a bit.
Example 2
struct ContentView: View {
@State var text: String = ""
var body: some View {
VStack {
Spacer()
TextField("Testing", text: $text)
}
}
}
Behavior: When the keyboard shows, the text field moves just above the keyboard.
Reason: There is a Spacer in the VStack, so the VStack will extend its height all the way to the height provided by the container. When the height of the container decreases because of the keyboard, the height of the VStack decreases too.
Example 3
struct ContentView: View {
@State var text: String = ""
var body: some View {
VStack {
TextField("Testing", text: $text)
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
Behavior: The text field moves up a little when the keyboard shows. The ignoresSafeArea modifier doesn't have any effect.
Reason: The ignoresSafeArea is applied on the VStack, while the VStack has a fixed height, and its bottom edge is far away from the bottom safe area, ignoresSafeArea has no effect. But the container does not ignoresSafeArea, so the height of the container still decreases when the keyboard shows.
Example 4
struct ContentView: View {
@State var text: String = ""
var body: some View {
VStack {
Spacer()
TextField("Testing", text: $text)
}
.ignoresSafeArea(.keyboard, edges: .bottom)
}
}
Behavior: The text field does not move when the keyboard shows, ignoresSafeArea is working.
Reason: This time the Spacer will make the VStack extend its height. Keep in mind the container doesn't ignoresSafeArea, so if the ignoresSafeArea modifier were not applied, when the keyboard shows, the bottom edge of the VStack will be just aligned to the top edge of the bottom safe area. So this time if ignoresSafeArea is applied, it will work and makes the VStack extend its height to ignore the keyboard bottom safe area.
Another example may help you understand how a parent view can respect safe areas while a subview can ignore them.
Example 5:
struct ContentView: View {
var body: some View {
ZStack {
Color.yellow
Color.green
.frame(width: 200)
.ignoresSafeArea()
}
.border(Color.blue, width: 10)
}
}
Behavior:
From the blue border, we see the parent ZStack respects safe areas while the green subview ignores safe areas.
Switch off Keyboard Avoidance
To switch off the iOS 14 keyboard avoidance, you can apply ignoresSafeArea to a parent view that extends its height, for example
struct ContentView: View {
@State var text: String = ""
var body: some View {
ZStack {
Color.clear
VStack {
TextField("Testing", text: $text)
}
}
.ignoresSafeArea(.keyboard)
}
}
In this example, the Color.clear extends its height, making the ZStack extend its height, so the ZStack will ignore the keyboard safe area. The VStack is placed at the center of the ZStack, thus not affected by the keyboard.