Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
570 views
in Technique[技术] by (71.8m points)

ios - SwiftUI in iOS14 Keyboard Avoidance Issues and IgnoresSafeArea Modifier Issues

iOS13 saw TextField not having any sort of keyboard avoidance handling in place. As such, we created our how keyboard avoidance mechanism which works well. We upgraded to iOS14 and this resulted in TextFields having keyboard avoidance built in. However, the keyboard avoidance does not seem to work as expected.

Issue 1 The first issue we experienced was keyboard avoidance not working expected for TextFields in and around the centre of the screen. Given this code:

struct ContentView: View {
    
    @State var text:String = ""
    
    var body: some View {
        
        TextField("Testing", text: $text)

    }
}

On an and iPhone 8 Plus the Textfield is moved up. In our opinion this shouldn't be happening as the TextField will not be hidden by the keyboard and as such it should remain in the same place.

Question 1: Is this a bug and should it be reported to Apple? We believe this to be a bug / issue.

We have found that using the following:

struct ContentView: View {
    
    @State var text:String = ""
    
    var body: some View {
        
        VStack {
            Spacer()
            TextField("Testing", text: $text)
        }
        
    }
}

The TextField will be moved just above the keyboard. Which is the expected behaviour. We've also found that with the code below:

struct ContentView: View {
    
    @State var text:String = ""
    
    var body: some View {
        
        VStack {
            TextField("Testing", text: $text)
            Spacer()
        }
        
    }
}

The TextField is not moved as it would never be covered by the TextField. Once again, this is the behaviour we expect. However, any TextField in and around the centre of the screen it would seem that the keyboard avoidance moves the TextField in scenario's where it shouldn't.

Issue 2 Our app holds TextFields in and around the centre on certain screens and as such the issue discovered above simply adds to a poor user experience and so we looked to "switch off" this keyboard avoidance provided to us. We looked to use the ignoresSafeArea modifier as follows:

struct ContentView: View {
    
    @State var text:String = ""
    
    var body: some View {

        if #available(iOS 14.0, *) {
            
            VStack {
                TextField("Testing", text: $text)
            }
            .ignoresSafeArea(.keyboard, edges: .bottom)

            
        } else {
            // Fallback on earlier versions
            // Our iOS13 Code
        }
                
    }
}

The observed result is that the modifier simply doesn't work. The TextField is still moved upwards. However when using something like this:

struct ContentView: View {
    
    @State var text:String = ""
    
    var body: some View {

        if #available(iOS 14.0, *) {
            
            VStack {
                Spacer()
                TextField("Testing", text: $text)
            }
            .ignoresSafeArea(.keyboard, edges: .bottom)

            
        } else {
            // Fallback on earlier versions
            // Our iOS13 Code
        }
                
    }
}

the ignoresSafeArea works and so this leads to the second question:

Question 2 Is there are bug with the ignoresSafeArea modifier as well? Is this something that should be reported?

It would seem that there is an underlying issue with TextFields in and around the centre of the screen?

Question 3 Anyone know of ways around these issues? Because right now it's a huge problem on iOS14. The keyboard avoidance doesn't work and any attempt to try and switch it off doesn't work either.

We are using Xcode 12.0 (12A7209)

Update

We have found that wrapping the TextField in a Geometry Reader that this seems to "switch off" Keyboard Avoidance for a TextField. However, we believe this to be a delightful hack that fixes the problem in one way but then also exposes that Keyboard Avoidance doesn't work in Geometry readers which could be a bug / issue for other people...

struct ContentView: View {
    
    @State var text:String = ""
    
    var body: some View {

        if #available(iOS 14.0, *) {
            
            GeometryReader { _ in
                VStack {
                    Spacer().frame(height:500) //Compensate for other Views in the Stack
                    TextField("Testing", text: $text)
                }
            }
            
        } else {
            // Fallback on earlier versions
            // Our iOS13 Code
        }
                
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The behaviors you described are all expected. We need to understand three things:

  1. SwiftUI layout system
  2. Keyboard safe area
  3. 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, Spacers, Colors, GeometryReaders, 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:

Screen shot

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...