macosswiftuitextfieldswiftui-texteditor

How Do I Correctly Style SwiftUI TextEditor


I'm working on an app with forms for the user, and I wanted to style my TextField and TextEditor instances the same. The former seem easy enough but I'm not convinced I'm constructing the latter correctly (Apple docs suggesting something like the below to be used):

private struct FlippableFieldEditorStyle: TextEditorStyle {
    
    @Binding var isBordered: Bool
    
    @Binding var text: String
    
    func makeBody(configuration: Configuration) -> some View {
        TextEditor(text: $text)
            .frame(maxHeight: 100)
            .modifier(BaseTextEntryModifier(isBordered: $isBordered))
    }
}

And to use it:

TextEditor(text: $textEntryBinding)
    .textEditorStyle(FlippableFieldEditorStyle(isBordered: $isEditing,
                                                  text: $textEntryBinding))

But this can't be right, can it? I appear to have to pass the textEntryBinding iVar twice. How do I access the bound variable in the TextEditorStyle code? I've tried using:

private struct FlippableFieldStyle: TextEditorStyle {
    @Binding var isBordered: Bool
    
    func _body(configuration: TextField<Self._Label>) -> some View {
        
        configuration.modifier(BaseTextEntryModifier(isBordered: $isBordered))
    }
}

which is just like for the TextFieldStyle, but this won't compile. I admit to very much struggling with the concept of Configuration...


Solution

  • Short Answer:

    TextFieldStyle isn't ready to be used in production and TextEditorStyle is incomplete.

    Long Answer:

    TextFieldStyle's _body is still private API (not meant for production) and TextEditorStyle is available in public API but does not make available the "value" like the other styles do.

    Other styles make available their value via the configuration for example ToggleStyle

    private struct FlippableFieldStyle: ToggleStyle {
        func makeBody(configuration: Configuration) -> some View {
            switch configuration.isOn {
            case true:
                configuration.label
                    .tint(.gray)
            case false:
                configuration.label
                    .tint(.green)
            }
        }
    }
    

    You can find all the available properties in `ToggleStyleConfiguration'

    The TextEditorStyle is public API but TextEditorConfiguration doesn't have any available properties.

    private struct FlippableFieldStyle: TextEditorStyle {
        func makeBody(configuration: Configuration) -> some View {
            configuration.text //Nothing here to modify
        }
    }
    

    Your solution where you put another TextEditor in the style is a workaround but if we were to dig you now likely have 2 TextEditor's in the hierarchy, the one you are styling and the one you are presenting in the style.

    The only available "right" solution seems to be a custom view for the TextEditor

    private struct FlippableFieldEditorStyle: View {
        @Binding var isBordered: Bool
        
        @Binding var text: String
        
        var body: some View {
            TextEditor(text: $text)
                .frame(maxHeight: 100)
        }
    }