PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0

Monday, August 22, 2022

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

 August 22, 2022     environment-variables, ios, swiftui     No comments   

Issue

I am trying to achieve hiding keyboard when .onTap anywhere in screen by using new .focused() methods which came with iOS15 (SwiftUI 3).

First declare my 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 }
    }
}

then in MainApp, I put FocusState to pass nil to the path when I tapped anywhere in screen.

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

Then I create my 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()
        }
    }
    }
}

I also tried it with environment variables but @FocusState PropertyWrapper causes problems again.


Solution

A FocusedValueKey is for changed value (ex. if you would want to pass entered text or state value), binding to focused state is not changed and does not fit concept (so signature is not matched).

As far as I can understand your goal it can be achieved by direct focused state injection into ContentView.

Here is an approach (w/o much refactoring of your code). Tested with Xcode 13 / iOS 15

demo

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()
            }
        }
    }
}

Update:

Here is an approach based on EnvironmentValues. Tested in same environment.

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()
            }
        }
    }
}

Note: for demo simplicity environment value is force-unwrapped, but for real project it is better to check conditionally if value is present and add modifier if environment is not nil.



Answered By - Asperi
Answer Checked By - Terry (PHPFixing Volunteer)
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Newer Post Older Post Home

0 Comments:

Post a Comment

Note: Only a member of this blog may post a comment.

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
Comments
Atom
Comments

Copyright © PHPFixing