简体   繁体   中英

TextFields using decimalKeyboard not scrolling to visible area when moved from a field using the normal keyboard in SwiftUI

Any idea why Field 3 and Field 5 do not get visible when active if the previous field was using the normal keyboard?

In the following code, if you tap on Field 1 and immediately after you tap on Field 3 or Field 5 , they will not be visible; they get hidden by the keyboard.

Please note that Field 3 and Field 5 use the decimalPad keyboard while the rest of the fields use the standard keyboard.

  struct TextFieldScrollingIssue: View {
    @State private var testInput:String = ""
    
    var body: some View {
        VStack{
            Form {
                TextField("Field 1", text:$testInput)
                Spacer()
                Spacer()
                Spacer()
                Spacer()
                Spacer()
                Spacer()
                Spacer()
                Section(header: Text("Section 1: ")) {
                    TextField("Field 2", text:$testInput)
                    TextField("Field 3", text:$testInput)
                        .keyboardType(.decimalPad)
                }
                
                Section(header: Text("Section 2: ")) {
                    TextField("Field 4", text:$testInput)
                    TextField("Field 5", text:$testInput)
                        .keyboardType(.decimalPad)
                }
            }
        }
    }
}

I think the scrolling mechanism is confused because you used the same variable for all of the TextFields . Obviously, in production, you would never have this scenario. The fix is simple, use different variables:

struct TextFieldScrollingIssue: View {
    @FocusState var isFocused: String?
    @State private var testInput:String = ""
    @State private var decimalInput:String = ""
    
    var body: some View {
        VStack{
            ScrollViewReader { scroll in
                Form {
                    TextField("Field 1", text:$testInput)
                        .id("Field 1")
                        .focused($isFocused, equals: "Field 1")
                    Text(isFocused?.description ?? "nil")
                    Spacer()
                    Spacer()
                    Spacer()
                    Spacer()
                    Spacer()
                    Spacer()
                    Section(header: Text("Section 1: ")) {
                        TextField("Field 2", text:$testInput)
                            .id("Field 2")
                            .focused($isFocused, equals: "Field 2")
                        TextField("Field 3", text:$decimalInput)
                            .id("Field 3")
                            .focused($isFocused, equals: "Field 3")
                            .keyboardType(.decimalPad)
                    }
                    
                    Section(header: Text("Section 2: ")) {
                        TextField("Field 4", text:$testInput)
                            .id("Field 4")
                            .focused($isFocused, equals: "Field 4")
                        TextField("Field 5", text:$decimalInput)
                            .id("Field 5")
                            .focused($isFocused, equals: "Field 5")
                            .keyboardType(.decimalPad)
                    }
                }
                .onChange(of: isFocused) { _ in
                    if let isFocused = isFocused {
                        DispatchQueue.main.async {
                            withAnimation {
                                scroll.scrollTo(isFocused)
                            }
                        }
                    }
                }
            }
        }
    }
}

Edit:

Based on the comment, I was able to reproduce. Edited code to use a ScrollviewReader , @FocusState and view ids to correct.

The problem is that when you tap on Field 3 or Field 5, the keyboard appears and pushes the form content upward. Since Field 3 and Field 5 are located at the bottom of the form, they are obscured by the keyboard. There are couple of ways you can use to fix this

  • Method 1

Use a ScrollView to wrap your Form and set the .keyboardDismissMode to .interactive . This will allow the user to dismiss the keyboard by swiping down on the ScrollView.

ScrollView(.vertical, showsIndicators: false) {
    Form {
        //...
    }
}.keyboardDismissMode(.interactive)
  • Method 2

Wrap the Form in a GeometryReader and use its safeAreaInsets to adjust the offset of the form when the keyboard appears

var body: some View {
    GeometryReader { geometry in
        VStack {
            Form {
                //...
            }
        }
        .offset(y: -geometry.safeAreaInsets.bottom)
    }
}
  • Method 3

Use the KeyboardResponder class to listen for keyboard events and adjust the offset of the form when the keyboard appears:

struct TextFieldScrollingIssue: View {
    @State private var testInput:String = ""
    @ObservedObject var keyboard = KeyboardResponder()

    var body: some View {
        VStack {
            Form {
                //...
            }
            .offset(y: -keyboard.currentHeight)
        }
    }
}

class KeyboardResponder: ObservableObject {
    private var notificationCenter: NotificationCenter
    @Published private(set) var currentHeight: CGFloat = 0

    init(center: NotificationCenter = .default) {
        notificationCenter = center
        notificationCenter.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        notificationCenter.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
    }

    @objc func keyBoardWillShow(notification: Notification) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            currentHeight = keyboardSize.height
        }
    }

    @objc func keyBoardWillHide(notification: Notification) {
        currentHeight = 0
    }
}

This issue is likely caused by the fact that the decimalPad keyboard type has a different height than the standard keyboard, and the layout of the view is not adjusting accordingly. One way to fix this issue would be to wrap the TextField views in a VStack and add a padding or offset modifier to the bottom of the VStack to adjust for the difference in keyboard height.

Another solution would be to use a third-party library like IQKeyboardManagerSwift which can automatically handle the keyboard and make sure the text fields are not hidden.

struct TextFieldScrollingIssue: View {
@State private var testInput:String = ""
var body: some View {
    VStack{
        Form {
            TextField("Field 1", text:$testInput)
            Spacer()
            Spacer()
            Spacer()
            Spacer()
            Spacer()
            Spacer()
            Spacer()
            Section(header: Text("Section 1: ")) {
                VStack {
                    TextField("Field 2", text:$testInput)
                    TextField("Field 3", text:$testInput)
                        .keyboardType(.decimalPad)
                    .offset(y: -100)  // add offset 
                }
            }
            
            Section(header: Text("Section 2: ")) {
                VStack {
                    TextField("Field 4", text:$testInput)
                    TextField("Field 5", text:$testInput)
                        .keyboardType(.decimalPad)
                    .offset(y: -100)  // add offset
                }
            }
        }
    }
}

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM