【问题标题】:How to navigate through SwiftUI TextFields by clicking on return button from keyboard?如何通过单击键盘上的返回按钮来浏览 SwiftUI 文本字段?
【发布时间】:2020-01-27 00:36:03
【问题描述】:

我正在使用 SwiftUI 的 TextField View。主要是我有2个问题:

  1. 在 Swift 中,我们可以像这样为 Storyboard 中的 TextField 设置 Return Key(Text Input Traits)Next 对吧?为此在 SwiftUI 中使用哪个修饰符?


  2. 我有两个文本字段,当我从键盘单击返回/下一个按钮时,如何导航到下一个文本字段?

任何人都可以使用 SwiftUI(不是 UIKit)来帮助解决这个问题吗?或者任何其他替代方法来执行此功能?

【问题讨论】:

    标签: ios textfield swiftui


    【解决方案1】:

    要解决您的两个问题,您需要使用 SwiftUI 中的 UIKit。首先,您需要使用 UIViewRepresentable 自定义 TextField。这是用于测试目的的示例代码,尽管代码不是那么优雅。我敢打赌,会有更强大的解决方案。

    1. 在自定义的TextFieldType中,键盘返回类型有 被设置。
    2. 通过使用对象绑定和委托方法 textFieldShouldReturn,View可以通过更新绑定变量来聚焦键盘。

    这里是示例代码:

    import SwiftUI
    
    struct KeyboardTypeView: View {
        @State var firstName = ""
        @State var lastName = ""
        @State var focused: [Bool] = [true, false]
    
        var body: some View {
            Form {
                Section(header: Text("Your Info")) {
                    TextFieldTyped(keyboardType: .default, returnVal: .next, tag: 0, text: self.$firstName, isfocusAble: self.$focused)
                    TextFieldTyped(keyboardType: .default, returnVal: .done, tag: 1, text: self.$lastName, isfocusAble: self.$focused)
                    Text("Full Name :" + self.firstName + " " + self.lastName)
                }
            }
    }
    }
    
    
    
    struct TextFieldTyped: UIViewRepresentable {
        let keyboardType: UIKeyboardType
        let returnVal: UIReturnKeyType
        let tag: Int
        @Binding var text: String
        @Binding var isfocusAble: [Bool]
    
        func makeUIView(context: Context) -> UITextField {
            let textField = UITextField(frame: .zero)
            textField.keyboardType = self.keyboardType
            textField.returnKeyType = self.returnVal
            textField.tag = self.tag
            textField.delegate = context.coordinator
            textField.autocorrectionType = .no
    
            return textField
        }
    
        func updateUIView(_ uiView: UITextField, context: Context) {
            if isfocusAble[tag] {
                uiView.becomeFirstResponder()
            } else {
                uiView.resignFirstResponder()
            }
        }
    
        func makeCoordinator() -> Coordinator {
            Coordinator(self)
        }
    
        class Coordinator: NSObject, UITextFieldDelegate {
            var parent: TextFieldTyped
    
            init(_ textField: TextFieldTyped) {
                self.parent = textField
            }
    
            func updatefocus(textfield: UITextField) {
                textfield.becomeFirstResponder()
            }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    
                if parent.tag == 0 {
                    parent.isfocusAble = [false, true]
                    parent.text = textField.text ?? ""
                } else if parent.tag == 1 {
                    parent.isfocusAble = [false, false]
                    parent.text = textField.text ?? ""
             }
            return true
            }
    
        }
    }
    

    输出:

    【讨论】:

    • 很好的解决方案。谢谢你。这是一个常见的用例,尤其是在表单中。没有更快的解决方案来解决这个问题吗?
    • 这似乎只适用于.default 类型。或者至少它不适用于numberPaddecimalPad。如果可能的话,关于如何使这些工作的任何想法?
    • 这可以通过现在使用 FocusState 和闭包的纯 SwiftUI 来实现 - stackoverflow.com/a/70293614/5632572 OP 说他们只有 2 个文本字段,所以在这种情况下,Razib 的这个带有 Enum 的解决方案更好
    【解决方案2】:

    截至 2021 年 6 月 15 日,它仍处于测试阶段。


    iOS 15.0+

    macOS 12.0+, Mac 催化剂 15.0+, tvOS 15.0+, watchOS 8.0+

    使用为视图设置提交标签的submitLabel(_:) 视图修饰符。它采用SubmitLabel中指定的预定义大小写

    使用.next。它定义了一个带有 “Next” 文本的提交标签。

    使用onFocus(_:) 查找修改后的视图层次结构(在本例中为TextField)失去焦点的时间。完成后,将焦点放在下一个视图上 (SecureField)

    struct LoginForm: View {
        enum Field: Hashable {
            case usernameField
            case passwordField
        }
        
        @State private var username = ""
        @State private var password = ""
        @FocusState private var focusedField: Field?
        
        var body: some View {
            Form {
                TextField("Username", text: $username)
                    .focused($focusedField, equals: .usernameField)
                    .submitLabel(.next)
                    .onFocus { isFocused in
                        if (!isFocused) {
                            focusedField = .passwordField
                        }
                    }
                
                SecureField("Password", text: $password)
                    .focused($focusedField, equals: .passwordField)
                    .submitLabel(.done)
            }
        }
    }
    
    

    更新

    我在xCode version 13.0 beta 5 (13A5212g) 中对此进行了测试。它有效。

    【讨论】:

    • 点击下一个按钮时是否应该转到下一个文本字段?如果是这样,它对我不起作用:/
    • .onFocus 已被弃用。它不会被释放。
    • 我在 Apple 最新的 beta 文档中读到了它。 .focused 还是不错的。 @FocusState 还是不错的。您需要将 .onChange 与 FocusState 一起使用,而不是 .onFocus。
    • Yeh onFocus 已被删除。知道改用什么吗?
    • 好的,我们可以使用.onSubmit()
    【解决方案3】:

    你不能,SwiftUI 中还没有响应者链的概念。您不能以编程方式启动对任何 View 的关注,因为它们实际上并不是视图本身,只是描述应如何设置视图的结构。我猜它最终可能会通过EnvironmentValues 暴露(如截断、自动更正等),但目前不存在。

    【讨论】:

    • 这个已经改变了,现在有什么办法可以解决这个问题!?
    【解决方案4】:

    根据 Razib Mollick 的回答和 https://www.hackingwithswift.com/forums/100-days-of-swiftui/jump-focus-between-a-series-of-textfields-pin-code-style-entry-widget/765

    我为文本字段数组提出了以下实现。

    struct NextLineTextField: UIViewRepresentable {
    @Binding var text: String
    @Binding var selectedField: Int
    
    var tag: Int
    var keyboardType: UIKeyboardType = .asciiCapable
    var returnKey: UIReturnKeyType = .next
    
    func makeUIView(context: UIViewRepresentableContext<NextLineTextField>) -> UITextField {
        let textField = UITextField(frame: .zero)
        textField.delegate = context.coordinator
        textField.keyboardType = keyboardType
        textField.returnKeyType = returnKey
        textField.tag = tag
        return textField
    }
    
    func makeCoordinator() -> NextLineTextField.Coordinator {
        return Coordinator(text: $text)
    }
    
    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<NextLineTextField>) {
        uiView.text = text
        context.coordinator.newSelection = { newSelection in
            DispatchQueue.main.async {
                self.selectedField = newSelection
            }
        }
    
        if uiView.tag == self.selectedField {
            uiView.becomeFirstResponder()
        }
    }
    
    class Coordinator: NSObject, UITextFieldDelegate {
    
        @Binding var text: String
        var newSelection: (Int) -> () = { _ in }
    
        init(text: Binding<String>) {
            _text = text
        }
    
        func textFieldDidChangeSelection(_ textField: UITextField) {
            DispatchQueue.main.async {
                self.text = textField.text ?? ""
            }
        }
        
        func textFieldDidBeginEditing(_ textField: UITextField) {
            self.newSelection(textField.tag)
        }
    
        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            if textField.returnKeyType == .done {
                textField.resignFirstResponder()
            } else {
                self.newSelection(textField.tag + 1)
            }
            return true
        }
      }
    }
    

    然后将表单元素设为

    class FieldElement: ObservableObject, Identifiable {
    var id = UUID()
    var title = ""
    @Published var value = ""
    var keyboard: UIKeyboardType = .asciiCapable
    var returnType: UIReturnKeyType = .next
    
    init(title: String, value: String = "", keyboard: UIKeyboardType = 
        .asciiCapable, returnType: UIReturnKeyType = .next) {
        self.title = title
        self.value = value
        self.keyboard = keyboard
        self.returnType = returnType
      }
    }
    

    为了实现

    struct FormView: View {
    
    @State var formElements: [FieldElement] = [
        FieldElement(title: "Name"),
        FieldElement(title: "Address"),
        FieldElement(title: "Phone Number"),
        FieldElement(title: "Email Address", keyboard: .emailAddress, returnType: 
    .done),
    ]
    
    @State var selectedField = 0
    
    var body: some View {
        VStack(alignment: .leading) {
            ForEach(Array(zip(formElements.indices, formElements)), id: \.0) { 
            index, element in
                VStack(alignment: .leading, spacing: 0) {
                    Text(element.title)
    
                    NextLineTextField(text: self.$formElements[index].value,
                        selectedField: self.$selectedField,
                        tag: index,
                        keyboardType: element.keyboard,
                        returnKey: element.returnType)
                        .frame(height: 35)
                        .frame(maxWidth: .infinity)
                        .overlay(
                            RoundedRectangle(cornerRadius: 8)
                                .stroke(Color.gray.opacity(0.5), lineWidth: 0.7)
                        )
                }.padding(.bottom, 4)
            }
    
            Button(action: {
                print(self.formElements.map({ $0.value }))
            }) {
                Text("Print Entered Values")
                    .foregroundColor(Color.white)
                    .font(.body)
                    .padding()
            }.frame(height: 50)
                .background(Color.green)
                .cornerRadius(8)
                .padding(.vertical, 10)
            Spacer()
        }.padding()
      }
    }
    

    如果这很难导航,请随时查看 https://github.com/prakshapan/Utilities/blob/master/FormView.swift

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-07
      相关资源
      最近更新 更多