focused modifier

I'm trying to make a custom TextField which eventually show errors when loosing focus in a normal form, foreach TextField, I write:

TextField(label, text: $text)
      .focused($checkoutInFocus, equals: <some Value>)
Text(textError)
            .foregroundColor(.red)
            .frame(height: textError == "" ? 0 : 20)

(I want to precise it's not all: I have many modifiers for the TextField and it becomes very difficult to read when there are many TextFields)

So I thought it would be a good idea to make a special view for each Textfield:

struct TextFieldWithError: View {
    var label: String
    @Binding var text:  String
    @Binding var textError: String
    @FocusState.Binding var isFocused: Bool
    
    init(label: String, text: Binding<String>, textError: Binding<String>, isFocused: FocusState<Bool>.Binding) {
        self.label = label
        self._text = text
        self._textError = textError
        self._isFocused = isFocused
    }
    
    var body: some View {
        TextField(label, text: $text)
            .modifier(TextFieldStyle())
            .focused($isFocused)
        Text(textError)
            .foregroundColor(.red)
            .frame(height: textError == "" ? 0 : 20)
    }
}

My problem is that I don't find the way to act on the focusState.

In the mainView I write:

TextFieldWithError(label: "Email", text: $email, textError: $emailError, isFocused: checkoutInFocus == .<some Value>)

in place of the modifier

.focused($checkoutInFocus, equals: .email)

but the syntax is obviously not good: I get the error:

Cannot convert value of type 'Bool' to expected argument type 'FocusState<Bool>.Binding'

Do you think there is a way?

Accepted Reply

I can send it with pleasure, by mail because code is very dense, but I think I found what was wrong:

Apple says about @FocusState:

A property wrapper type that can read and write a value that SwiftUI updates as the placement of focus within the scene changes.

to use this FocusState property, I used an enum declaration which was contained in the main view. But if I want to make a TextfieldWithError view, which use this enum, I have to declare this enum alone, not in my main View:

enum LoginFocusable: Hashable {
    case email
    case pwd
    case none
    
    var string: String {
        switch self {
        case .email: return "email"
        case .pwd: return "pwd"
        default: return ""
        }
    }
    
    var nextToVerif: LoginFocusable {
        switch self {
        case .email: return .pwd
        case .pwd: return .email
        default: return .none
        }
    }
}

that's the first point

So, in TextfieldInError, I can have the declaration:

struct TextFieldWithError: View {
    var label: String
    @Binding var text:  String
    @Binding var textError: String
    @FocusState.Binding var focus: LoginFocusable?
}

But t's not enough, because I will need to use the .focused modifier in TextFieldWithError so, I have to declare another property in TextFieldWithError: focusId of type LoginFocusable, so I will be able to user the .focused modifier

So my code will be

struct TextFieldWithError: View {
    var label: String
    @Binding var text:  String
    @Binding var textError: String
    var focusId:  LoginFocusable?
    @FocusState.Binding var focus: LoginFocusable?
    
    var body: some View {
        TextField(label, text: $text)
            .modifier(TextFieldStyle())
            .focused($focus, equals: focusId)
        Text(textError)
            .foregroundColor(.red)
            .frame(height: textError == "" ? 0 : 20)
    }
}

Now, in the main view, I can declare:

TextFieldWithError(label: "Email", text: $email, textError: $emailError, focusId: .email, focus: $checkoutInFocus)

Of course, the logic of focus handle has to be written in this main View

  • In the previous solution, I made a mistake: if I want to have TextFieldInError being working with other view, I can't link it with LoginFocusable, but I have to make a Generic:

    struct TextFieldWithError<T: Hashable>: View { var label: String @Binding var text: String @Binding var textError: String var focusId: T? @FocusState.Binding var focus: T? }

Add a Comment

Replies

Did you try:

isFocused: $checkoutInFocus == .<some Value>)
Add a Comment

Could you post the complete code so I can test ?

I can send it with pleasure, by mail because code is very dense, but I think I found what was wrong:

Apple says about @FocusState:

A property wrapper type that can read and write a value that SwiftUI updates as the placement of focus within the scene changes.

to use this FocusState property, I used an enum declaration which was contained in the main view. But if I want to make a TextfieldWithError view, which use this enum, I have to declare this enum alone, not in my main View:

enum LoginFocusable: Hashable {
    case email
    case pwd
    case none
    
    var string: String {
        switch self {
        case .email: return "email"
        case .pwd: return "pwd"
        default: return ""
        }
    }
    
    var nextToVerif: LoginFocusable {
        switch self {
        case .email: return .pwd
        case .pwd: return .email
        default: return .none
        }
    }
}

that's the first point

So, in TextfieldInError, I can have the declaration:

struct TextFieldWithError: View {
    var label: String
    @Binding var text:  String
    @Binding var textError: String
    @FocusState.Binding var focus: LoginFocusable?
}

But t's not enough, because I will need to use the .focused modifier in TextFieldWithError so, I have to declare another property in TextFieldWithError: focusId of type LoginFocusable, so I will be able to user the .focused modifier

So my code will be

struct TextFieldWithError: View {
    var label: String
    @Binding var text:  String
    @Binding var textError: String
    var focusId:  LoginFocusable?
    @FocusState.Binding var focus: LoginFocusable?
    
    var body: some View {
        TextField(label, text: $text)
            .modifier(TextFieldStyle())
            .focused($focus, equals: focusId)
        Text(textError)
            .foregroundColor(.red)
            .frame(height: textError == "" ? 0 : 20)
    }
}

Now, in the main view, I can declare:

TextFieldWithError(label: "Email", text: $email, textError: $emailError, focusId: .email, focus: $checkoutInFocus)

Of course, the logic of focus handle has to be written in this main View

  • In the previous solution, I made a mistake: if I want to have TextFieldInError being working with other view, I can't link it with LoginFocusable, but I have to make a Generic:

    struct TextFieldWithError<T: Hashable>: View { var label: String @Binding var text: String @Binding var textError: String var focusId: T? @FocusState.Binding var focus: T? }

Add a Comment