如何使用 FocusedValue 键路径隐藏 TextFields 键盘 onTap?

How to hide TextFields keyboard onTap by using FocusedValue key path?

我正在尝试通过使用 iOS15 (SwiftUI 3) 随附的新 .focused() 方法在屏幕上任何位置的 .onTap 时实现隐藏键盘。

首先声明我的 FocusedValueKey

struct FocusedTextField: FocusedValueKey {
    
    enum Field: Hashable {
        case firstName
        case lastName
        case emailAddress
    }
    
    typealias Value = FocusState<Field>.Binding
}

extension FocusedValues {
    var textField: FocusedTextField.Value? {
        get { self[FocusedTextField.self] }
        set { self[FocusedTextField.self] = newValue }
    }
}

然后在 MainApp 中,当我点击屏幕中的任意位置时,我将 FocusState 设置为将 nil 传递给路径。

struct TextFieldTestApp: App {
    
    @FocusState private var state: FocusedTextField.Field?
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onTapGesture { state = nil }
                .focusedValue(\.textField, $state)
        }
    }
}

然后我创建我的 ContentView

struct ContentView: View {

    @State private var firstName = ""
    @State private var lastName = ""
    @State private var emailAddress = ""

    // Error: Key path value type 'FocusedTextField.Value?' (aka 'Optional<FocusState<FocusedTextField.Field>.Binding>')
    // cannot be converted to contextual type 'Binding<Value>?'
    @FocusedBinding(\.textField) var focusedTextField 

    var body: some View {
        ZStack {
            Color.red
        VStack {
            Spacer()
            TextField("Enter your first name", text: $firstName)
                // Error: Type 'Hashable' has no member 'firstName'
                .focused($focusedTextField, equals: .firstName)
                .textContentType(.givenName)
                .submitLabel(.next)

            TextField("Enter your last name", text: $lastName)
                // Error: Type 'Hashable' has no member 'lastName'
                .focused(focusedTextField, equals: .lastName)
                .textContentType(.familyName)
                .submitLabel(.next)

            TextField("Enter your email address", text: $emailAddress)
                // Error: Type 'Hashable' has no member 'emailAddress'
                .focused(focusedTextField, equals: .emailAddress)
                .textContentType(.emailAddress)
                .submitLabel(.join)
            Spacer()
        }
    }
    }
}

我也尝试过使用环境变量,但@FocusState PropertyWrapper 再次导致问题。

A FocusedValueKey 用于已更改的值(例如,如果您想传递输入的文本或状态值),绑定到聚焦状态未更改且不符合概念(因此签名不匹配) .

据我所知,您的目标可以通过直接将聚焦状态注入 ContentView.

来实现

这是一种方法(w/o 大量重构您的代码)。测试 Xcode 13 / iOS 15

struct TextFieldTestApp: App {
    
    @FocusState private var state: FocusedTextField.Field?
    
    var body: some Scene {
        WindowGroup {
            ContentView(focusedTextField: $state)
                .onTapGesture { state = nil }
        }
    }
}

struct ContentView: View {

    @State private var firstName = ""
    @State private var lastName = ""
    @State private var emailAddress = ""

    var focusedTextField: FocusState<FocusedTextField.Field?>.Binding

    var body: some View {
        ZStack {
            Color.red
            VStack {
                Spacer()
                TextField("Enter your first name", text: $firstName)
                    .focused(focusedTextField, equals: .firstName)
                    .textContentType(.givenName)
                    .submitLabel(.next)

                TextField("Enter your last name", text: $lastName)
                    .focused(focusedTextField, equals: .lastName)
                    .textContentType(.familyName)
                    .submitLabel(.next)

                TextField("Enter your email address", text: $emailAddress)
                    .focused(focusedTextField, equals: .emailAddress)
                    .textContentType(.emailAddress)
                    .submitLabel(.join)
                Spacer()
            }
        }
    }
}

更新:

这是一种基于 EnvironmentValues 的方法。在相同的环境中测试。

struct TextFieldTestApp: App {
    
    @FocusState private var state: FocusedTextField.Field?
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onTapGesture { state = nil }
                .environment(\.textField, $state)
        }
    }
}

struct FocusedTextField: EnvironmentKey {
    static var defaultValue: FocusState<Field?>.Binding? = nil

    enum Field: Hashable {
        case firstName
        case lastName
        case emailAddress
    }

    typealias Value = FocusState<Field?>.Binding?
}

extension EnvironmentValues {
    var textField: FocusedTextField.Value {
        get { self[FocusedTextField.self] }
        set { self[FocusedTextField.self] = newValue }
    }
}

struct ContentView: View {

    @State private var firstName = ""
    @State private var lastName = ""
    @State private var emailAddress = ""

    @Environment(\.textField) private var focusedTextField

    var body: some View {
        ZStack {
            Color.red
            VStack {
                Spacer()
                TextField("Enter your first name", text: $firstName)
                    .focused(focusedTextField!, equals: .firstName)                     
                    .textContentType(.givenName)
                    .submitLabel(.next)

                TextField("Enter your last name", text: $lastName)
                    .focused(focusedTextField!, equals: .lastName)
                    .textContentType(.familyName)
                    .submitLabel(.next)

                TextField("Enter your email address", text: $emailAddress)
                    .focused(focusedTextField!, equals: .emailAddress)
                    .textContentType(.emailAddress)
                    .submitLabel(.join)
                Spacer()
            }
        }
    }
}

注意:为了演示简单,环境值是强制展开的,但对于实际项目,最好有条件地检查值是否存在,如果环境不为零则添加修饰符。