SwiftUI属性包装器之FocusState

所属分类:ios | 发布于 2023-12-10 16:41:50

在iOS15中,SwiftUI提供了FocusState属性包装器,用来帮助我们判断该视图内的TextField是否获得焦点。通过focused将FocusState与特定的TextField关联起来。

先看代码:

import SwiftUI

struct SignInView: View {
    @FocusState private var isEmailFocused: Bool
    @State private var email = ""

    var body: some View {
        NavigationView {
            Form {
                TextField("email", text: $email, prompt: Text("email"))
                    .focused($isEmailFocused)
            }
            .navigationTitle("Sign in")
            .onChange(of: isEmailFocused) { newValue in
                print(newValue)
            }
        }
    }
}

在上面的代码中,我们定义了一个isEmailFocused的属性包装器,在TextField组件上添加focused()修饰符,并绑定到定义的isEmailFocused属性包装器,当TextField获取到焦点时,isEmailFocused被设置为true,当TextField失去焦点时,isEmailFocused被设置为false。

可以定义多个属性包装器,如下:

import SwiftUI

struct SignInView: View {
    @FocusState private var isEmailFocused: Bool
    @FocusState private var isPasswordFocused: Bool

    @State private var email = ""
    @State private var password = ""

    var body: some View {
        NavigationView {
            Form {
                TextField("email", text: $email, prompt: Text("email"))
                    .focused($isEmailFocused)
                SecureField("password", text: $password, prompt: Text("password"))
                    .focused($isPasswordFocused)
            }
            .navigationTitle("Sign in")
        }
    }
}

在上面的代码中,定义了两个FocusState属性包装器,用来管理email和password的状态。

再进一步,点击登录按钮发现字段为空时,字段自动获取焦点,如下:

import SwiftUI

struct SignInView: View {
    @FocusState private var isEmailFocused: Bool
    @FocusState private var isPasswordFocused: Bool

    @State private var email = ""
    @State private var password = ""

    var body: some View {
        NavigationView {
            Form {
                TextField("email", text: $email, prompt: Text("email"))
                    .focused($isEmailFocused)
                SecureField("password", text: $password, prompt: Text("password"))
                    .focused($isPasswordFocused)

                Button("login") {
                    if email.isEmpty {
                        isEmailFocused = true
                    } else if password.isEmpty {
                        isPasswordFocused = true
                    } else {
                        isPasswordFocused = false
                        isEmailFocused = false

                        login()
                    }
                }
            }
            .navigationTitle("Sign in")
        }
    }

    private func login() {
        // your logic here
    }
}

 

在复杂的视图中定义很多个FocusState包装器会显得很麻烦,幸运的是,FocusState不仅支持Bool类型,还支持任何实现了Hashable协议的类型。这样,我们就可以定义一个实现了Hashable协议的enum类型来管理所有的focused状态。

示例代码如下:

struct ContentView: View {
    @State private var email = ""
    @State private var password = ""
    @FocusState private var focus: FocusableField?

enum FocusableField: Hashable {
    case email
    case password
}

    var body: some View {
        NavigationView {
            Form {
                TextField("email", text: $email, prompt: Text("email"))
                    .focused($focus, equals: .email)
                SecureField("password", text: $password, prompt: Text("password"))
                    .focused($focus, equals: .password)
                Button("login", action: login)
            }
            .toolbar {
                ToolbarItem(placement: .keyboard) {
                    Button("next") {
                        if email.isEmpty {
                            focus = .email
                        } else if password.isEmpty {
                            focus = .password
                        } else {
                            focus = nil
                        }
                    }
                }
            }
            .navigationTitle("Sign in")
            .defaultFocus($focus, .email)
        }
    }

    private func login() {
        // your logic here
    }
}

在上面的代码中,任何一个焦点的改变都会更新到focus属性包装器上。

需要注意的是,Hashable enum类型的属性包装器必须设置为可选类型,因为在大部分时间这些元素都是没有焦点的。

 

传递@FocusState属性包装器给子视图

struct View1: View {
  enum Field {
    case username, password
  }

  @State var passwordText: String = ""
  @FocusState var focusedField: Field?

  var body: some View {
    View2(text: $passwordText, placeholder: "Password", focused: $focusedField)
  }
}

struct View2: View {
  @Binding var text: String
  let placeholder: String
  var focused: FocusState<View1.Field?>.Binding     // << here !!

  var body: some View {
    HStack {
        TextField(placeholder, text: $text)
            .frame(minHeight: 44)
            .padding(.leading, 8)
            .focused(focused, equals: .password)     // << here !!
        if text.count > 0 {
            Image(systemName: "xmark.circle.fill")
                .font(.headline)
                .foregroundColor(.secondary)
                .padding(.trailing, 8)
        }

    }
  }
}

 

视图显示,立刻让指定的TextField获取焦点并弹出键盘

struct OnFocusDemo:View{
    @FocusState var focus:FocusedField?
    @State var name = ""
    @State var password = ""
    var body: some View{
        List{
            TextField("name:",text:$name)
                .focused($focus, equals: .name)
            SecureField("password:",text:$password)
                .focused($focus,equals: .password)
        }
        .onAppear{
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5){
                focus = .name
            }
        }
    }

    enum FocusedField:Hashable{
        case name,password
    }
}

 

文哥博客(https://wenge365.com)属于文野个人博客,欢迎浏览使用

联系方式:qq:52292959 邮箱:52292959@qq.com

备案号:粤ICP备18108585号 友情链接