如何通过点击键盘上的返回按钮在SwiftUI文本字段中导航?

16

我正在使用SwiftUI的TextField View,主要有两个问题:

  1. 在Swift中,我们可以像这样从storyboard为TextField设置Return Key(文本输入特性)Next。那么,在SwiftUI中应该使用哪种修饰符?

    enter image description here

  2. 我有两个Textfield,当我点击键盘上的return / next按钮时,如何导航到下一个TextField?

是否有人可以使用SwiftUI(而不是UIKit)来解决这个问题?或者有其他方法来实现此功能吗?

6个回答

20

为了解决你的两个问题,你需要使用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
        }

    }
}

输出: 在此输入图片描述


2
好的解决方案。谢谢。这是一个非常常见的用例,尤其是在表单中。难道没有更快速的解决方法吗? - Jonas Deichelmann
这似乎只适用于.default类型。或者至少对于numberPaddecimalPad都不起作用。如果有可能,有什么想法可以让它们工作吗? - mota
现在可以使用FocusState和闭包纯粹地使用SwiftUI来实现这一点- https://dev59.com/lFMH5IYBdhLWcg3wsBUS#70293614。 OP说他们只有2个文本字段,所以Razib的枚举解决方案在这种情况下更好。 - Michael Ellis

6

iOS 15.0+

macOS 12.0+, Mac Catalyst 15.0+, tvOS 15.0+,watchOS 8.0+

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

使用 .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)
        }
    }
}


当点击下一个按钮时,它是否应该跳转到下一个文本字段?如果是这样,对我来说它不起作用 :/ - czater
.onFocus已被弃用,不会再发布。 - adamek
我在苹果最新的测试文档中读到了这篇内容。.focused仍然可用。@FocusState仍然可用。你需要使用FocusState和.onChange代替.onFocus。 - adamek
我也能够在没有枚举的情况下实现这一点 - https://dev59.com/lFMH5IYBdhLWcg3wsBUS#70293614 - Michael Ellis
这是一篇博客文章的链接,分享了如何在SwiftUI 3中实现它(适用于iOS15+):https://swiftuirecipes.com/blog/navigate-fields-using-keyboard-in-swiftui - Ugo
显示剩余3条评论

1
您无法这样做,因为在SwiftUI中还没有响应者链的概念。您无法在任何View上以编程方式启动焦点,因为它们实际上并不是视图本身,只是描述如何设置视图的结构体。我猜它最终可能会通过EnvironmentValues(例如行截断,自动更正等)公开,但目前不存在。

14
这是否有所改变,现在有没有任何解决方法? - Learn2Code

1
iOS 15+开始,我们可以使用@FocusStateonSubmit()来处理键盘操作。

struct TextFieldFocus: View {
    enum Field {
        case userName, password
    }
    @State var field1 = ""
    @State var field2 = ""
    @FocusState var focusedField: Field?
    
    var body: some View {
        VStack(spacing: 20.0) {
            TextField("User name", text: $field1)
                .focused($focusedField, equals: .userName)
                .submitLabel(.next)
                .padding()
                .onSubmit {
                    focusedField = .password
                }
            
            SecureField("Password", text: $field2)
                .focused($focusedField, equals: .password)
                .submitLabel(.continue)
                .padding()
                .onSubmit {
                    focusedField = .userName
                }
        }
        .textFieldStyle(.roundedBorder)
    }
}


好的回答,但是去掉padding(),因为它不是必要的。 - CoolCrafts Bayarea

1

基于 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

0

我编写了一个小型库,可以在提交时自动导航到文本字段之间:FormView。它还具有以下有用的功能:

  • 根据指定规则验证文本字段
  • 基于指定规则防止不正确的输入

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接