Todd
Todd

Reputation: 1953

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...

Upvotes: 0

Views: 435

Answers (2)

user20325868
user20325868

Reputation: 315

As lorem ipsum stated TextEditor just isn't ready yet, but you can do some work arounds. Check out this article Hacking With Swift

private struct FlippableFieldStyle: View {

    @Binding var text: String

    var body: some View {
        TextEditor(text: $text)
        .scrollContentBackground(.hidden)
        .overlay(
            RoundedRectangle(cornerRadius: 10)
                .stroke(.primary, lineWidth: 2 / 3)
                .fill(Color.secondary.gradient.opacity(0.075))
                .opacity(0.3)
        )
    }
}

To use:

FlippableFieldStyle(text: $text)

Or make a modifier (My preferred way of dealing with it.)

    struct TextEditorBorderedBackgroundViewModifier: ViewModifier {
    
    @Binding var isBordered: Bool

    func body(content: Content) -> some View {
        if isBordered {
            
            content
                .scrollContentBackground(.hidden)
                .cornerRadius(10)
                .overlay(
                    RoundedRectangle(cornerRadius: 10)
                        .stroke(.primary, lineWidth: 2 / 3)
                        .fill(Color.secondary.gradient.opacity(0.075))
                        .opacity(0.3)
                )
        } else {
            content
        }
    }
}

extension View {
    func textEditorBorderedBackground(isBordered: Binding<Bool>) -> some View {
        self.modifier(TextEditorBorderedBackgroundViewModifier(isBordered: isBordered))
    }
}

to use

            TextEditor(text: $text)
            .textEditorBorderedBackground(isBordered: $showBorder)

Upvotes: 1

lorem ipsum
lorem ipsum

Reputation: 29242

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

Upvotes: 2

Related Questions