Как скрыть текстовые поля клавиатуры onTap с помощью пути к клавише FocusedValue?

#ios #swiftui #environment-variables

Вопрос:

Я пытаюсь добиться скрытия клавиатуры, когда .onTap в любом месте экрана с помощью новых методов .focused (), которые поставляются с iOS15 (SwiftUI 3).

Сначала объявите мою сфокусированную ценность

 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, чтобы передать ноль в путь, когда я нажимал в любом месте экрана.

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

Затем я создаю свое представление контента

 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 снова вызывает проблемы.

Ответ №1:

A FocusedValueKey для измененного значения (например, если вы хотите передать введенный текст или значение состояния), привязка к сфокусированному состоянию не изменяется и не соответствует концепции (поэтому подпись не соответствует).

Насколько я могу понять вашу цель, она может быть достигнута путем прямого целенаправленного введения состояния в ContentView .

Вот подход (без значительного рефакторинга вашего кода). Протестировано с 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()
            }
        }
    }
}
 

Примечание: для простоты демонстрации значение среды принудительно раскрывается, но для реального проекта лучше условно проверить, присутствует ли значение, и добавить модификатор, если среда не равна нулю.

Комментарии:

1. Внедрение будет беспорядочным во вложенных представлениях, вместо этого я попытался найти решение для свойств среды, которое может применяться ко всем экранам в приложении.

2. Подход, основанный на EnvironmentValues этом, не работает.