Ios 关注SwiftUI中的下一个TextField/SecureField

Ios 关注SwiftUI中的下一个TextField/SecureField,ios,swiftui,Ios,Swiftui,我在SwiftUI中构建了一个登录屏幕。当用户完成输入电子邮件时,我想重点关注密码SecureField。我该怎么做 struct LoginView: View { @State var username: String = "" @State var password: String = "" var body: some View { ScrollView { VStack {

我在SwiftUI中构建了一个登录屏幕。当用户完成输入电子邮件时,我想重点关注密码
SecureField
。我该怎么做

struct LoginView: View {
    @State var username: String = ""
    @State var password: String = ""

    var body: some View {
        ScrollView {
            VStack {
                TextField("Email", text: $username)
                    .padding()
                    .frame(width: 300)
                    .background(Color(UIColor.systemGray5))
                    .cornerRadius(5.0)
                    .padding(.bottom, 20)
                    .keyboardType(.emailAddress)

                SecureField("Password", text: $password)
                    .padding()
                    .frame(width: 300)
                    .background(Color(UIColor.systemGray5))
                    .cornerRadius(5.0)
                    .padding(.bottom, 20)

                Button(action: {

                }, label: {
                    Text("Login")
                        .padding()
                        .frame(width: 300)
                        .background((username.isEmpty || password.isEmpty) ? Color.gray : Color(UIColor.cricHQOrangeColor()))
                        .foregroundColor(.white)
                        .cornerRadius(5.0)
                        .padding(.bottom, 20)
                }).disabled(username.isEmpty || password.isEmpty)

当使用UIKit时,可以通过设置响应器链来实现这一点。这在SwiftUI中是不可用的,因此,在出现更复杂的焦点和响应系统之前,您可以使用的
onEditingChanged
changed

然后需要根据存储的状态变量管理每个字段的状态。它可能会比你想做的更多

幸运的是,通过使用UIViewRepresentable,您可以回到SwiftUI中的UIKit

以下是一些使用UIKit响应程序系统管理文本字段焦点的代码:

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
        }

    }
}
您可以参考这篇文章以获得有关此特定方法的更多信息


希望这有帮助

我对Gene Z.Ragan和Razib Mollick的答案有所改进。修复了一个崩溃,这允许任何数量的文本字段,支持密码,并使其成为自己的类

struct UITextFieldView: UIViewRepresentable {
    let contentType: UITextContentType
    let returnVal: UIReturnKeyType
    let placeholder: String
    let tag: Int
    @Binding var text: String
    @Binding var isfocusAble: [Bool]

    func makeUIView(context: Context) -> UITextField {
        let textField = UITextField(frame: .zero)
        textField.textContentType = contentType
        textField.returnKeyType = returnVal
        textField.tag = tag
        textField.delegate = context.coordinator
        textField.placeholder = placeholder
        textField.clearButtonMode = UITextField.ViewMode.whileEditing

        if textField.textContentType == .password || textField.textContentType == .newPassword {
            textField.isSecureTextEntry = true
        }

        return textField
    }

    func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.text = text

        if uiView.window != nil {
            if isfocusAble[tag] {
                if !uiView.isFirstResponder {
                    uiView.becomeFirstResponder()
                }
            } else {
                uiView.resignFirstResponder()
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UITextFieldDelegate {
        var parent: UITextFieldView

        init(_ textField: UITextFieldView) {
            self.parent = textField
        }

        func textFieldDidChangeSelection(_ textField: UITextField) {
            // Without async this will modify the state during view update.
            DispatchQueue.main.async {
                self.parent.text = textField.text ?? ""
            }
        }

        func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
            setFocus(tag: parent.tag)
            return true
        }

        func setFocus(tag: Int) {
            let reset = tag >= parent.isfocusAble.count || tag < 0

            if reset || !parent.isfocusAble[tag] {
                var newFocus = [Bool](repeatElement(false, count: parent.isfocusAble.count))
                if !reset {
                    newFocus[tag] = true
                }
                // Without async this will modify the state during view update.
                DispatchQueue.main.async {
                    self.parent.isfocusAble = newFocus
                }
            }
        }

        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            setFocus(tag: parent.tag + 1)
            return true
        }
    }
}

struct UITextFieldView_Previews: PreviewProvider {
    static var previews: some View {
        UITextFieldView(contentType: .emailAddress,
                       returnVal: .next,
                       placeholder: "Email",
                       tag: 0,
                       text: .constant(""),
                       isfocusAble: .constant([false]))
    }
}
struct UITextFieldView:UIViewRepresentable{
let contentType:UITextContentType
let returnVal:UIReturnKeyType
让占位符:字符串
让标记:Int
@绑定变量文本:字符串
@绑定变量可聚焦:[Bool]
func makeUIView(上下文:context)->UITextField{
设textField=UITextField(帧:.0)
textField.textContentType=contentType
textField.returnKeyType=returnVal
textField.tag=tag
textField.delegate=context.coordinator
textField.placeholder=占位符
textField.clearButtonMode=UITextField.ViewMode.whileEditing
如果textField.textContentType=.password | | textField.textContentType=.newPassword{
textField.issecurettentry=true
}
返回文本字段
}
func updateUIView(uiView:UITextField,context:context){
uiView.text=文本
如果uiView.window!=nil{
如果可聚焦[标记]{
if!uiView.isFirstResponder{
uiView.becomeFirstResponder()
}
}否则{
uiView.resignFirstResponder()的辞职
}
}
}
func makeCoordinator()->Coordinator{
协调员(自我)
}
类协调器:NSObject,UITextFieldDelegate{
变量父级:UITextFieldView
init(uTextField:UITextFieldView){
self.parent=textField
}
func textField didchangeSelection(textField:UITextField){
//如果没有async,这将在视图更新期间修改状态。
DispatchQueue.main.async{
self.parent.text=textField.text??“”
}
}
func textField应该开始编辑(textField:UITextField)->Bool{
setFocus(标记:parent.tag)
返回真值
}
func setFocus(标记:Int){
让reset=tag>=parent.isfocusAble.count | | tag<0
如果重置| |!parent.isfocusAble[标记]{
var newFocus=[Bool](repeatElement(false,count:parent.isfocusAble.count))
如果!重置{
newFocus[tag]=true
}
//如果没有async,这将在视图更新期间修改状态。
DispatchQueue.main.async{
self.parent.isfocusAble=newFocus
}
}
}
func textField应该返回(textField:UITextField)->Bool{
setFocus(标记:parent.tag+1)
返回真值
}
}
}
结构UITextFieldView\u预览:PreviewProvider{
静态var预览:一些视图{
UITextFieldView(内容类型:。电子邮件地址,
returnVal:。下一步,
占位符:“电子邮件”,
标签:0,
文本:。常量(“”),
isfocusAble:。常量([false]))
}
}
iOS 15 在iOS 15中,我们现在可以使用来控制应该关注哪个字段

以下是如何在键盘上方添加按钮以聚焦上一个/下一个字段的示例:

扩展内容视图{
私有枚举字段:Int,可大小写{
案例电子邮件、用户名、密码
}
私有函数focusPreviousField(){
focusedField=focusedField.map{
字段(rawValue:$0.rawValue-1)??密码
}
}
私有函数focusNextField(){
focusedField=focusedField.map{
字段(rawValue:$0.rawValue+1)??电子邮件
}
}
private func canFocusPreviousField()->Bool{
guard let currentFocusedField=focusedField else{
返回错误
}
返回currentFocusedField.rawValue>0
}
私有函数canFocusNextField()->Bool{
guard let currentFocusedField=focusedField else{
返回错误
}
返回currentFocusedField.rawValue

注意:从Xcode 13 beta 1开始,
@FocusState
表单中不起作用
/
列表
。这将在下一版本中修复。

谢谢,我能够扩展它以支持密码。我们无法修复
textfieldtypted
Width?当每次焦点移动到另一个textfield或键盘被解除时,在控制台中打印以下内容===AttributeGraph:通过属性105检测到的循环===。此外,当键盘消失且处于模式窗口时,行为也不是自然/平滑的。主视图上的键盘将临时显示,并消失,同时显示更多
===属性图形:通过属性105===
错误消息检测到的循环。这似乎是由于
DispatchQueue.main.async
中的以下部分代码
self.parent.isfocusAble=newFocus
造成的,我自己也不能说我看到了任何问题。我会继续
struct ContentView: View {
    @State private var email: String = ""
    @State private var username: String = ""
    @State private var password: String = ""

    @FocusState private var focusedField: Field?

    var body: some View {
        NavigationView {
            VStack {
                TextField("Email", text: $email)
                    .focused($focusedField, equals: .email)
                TextField("Username", text: $username)
                    .focused($focusedField, equals: .username)
                SecureField("Password", text: $password)
                    .focused($focusedField, equals: .password)
            }
            .toolbar {
                ToolbarItem(placement: .keyboard) {
                    Button(action: focusPreviousField) {
                        Image(systemName: "chevron.up")
                    }
                    .disabled(!canFocusPreviousField()) // remove this to loop through fields
                }
                ToolbarItem(placement: .keyboard) {
                    Button(action: focusNextField) {
                        Image(systemName: "chevron.down")
                    }
                    .disabled(!canFocusNextField()) // remove this to loop through fields
                }
            }
        }
    }
}
extension ContentView {
    private enum Field: Int, CaseIterable {
        case email, username, password
    }
    
    private func focusPreviousField() {
        focusedField = focusedField.map {
            Field(rawValue: $0.rawValue - 1) ?? .password
        }
    }

    private func focusNextField() {
        focusedField = focusedField.map {
            Field(rawValue: $0.rawValue + 1) ?? .email
        }
    }
    
    private func canFocusPreviousField() -> Bool {
        guard let currentFocusedField = focusedField else {
            return false
        }
        return currentFocusedField.rawValue > 0
    }

    private func canFocusNextField() -> Bool {
        guard let currentFocusedField = focusedField else {
            return false
        }
        return currentFocusedField.rawValue < Field.allCases.count - 1
    }
}