SJoshi
SJoshi

Reputation: 1976

Is it possible to override SwiftUI modifiers?

Knowing that with SwiftUI view modifiers, order matters - because each modifier is a part of a chain of modifiers, I was wondering if it was possible to reset/overwrite/override a modifier (or the whole chain?

Specifically, I'm wondering about Styles (groupBoxStyle, buttonStyle, etc). I have default styles that I want to use in 90% of my app, and a few pages will have slightly different styles for those widgets.

For example:

// Renders a button with the "light" style
Button("Hello world") {
}
.buttonStyle(LightButtonStyle())
.buttonStyle(DarkButtonStyle())

// Renders a button with the "dark" style
Button("Hello world") {
}
.buttonStyle(DarkButtonStyle())
.buttonStyle(LightButtonStyle())

In those cases, I would actually like the 2nd modifier to be used, but the 1st takes over and subsequent styles don't work.

Note: In my actual app, none of my use cases are this trivial - this is just the simplest proof of concept.

The workaround(s) I have are that I create separate LightButton and DarkButton views, but that feels very inelegant (and becomes a mess when I have 5-6 variants of each component).

Alternatively, I have a custom MyButton(myStyle: ButtonStyle = .myDefaultStyle), but since this is a forms app, there are about 50-60 locations where something like that needs to be updated (instead of applying a modifier at a top level and letting that cascade through).

Edit: I should note, where I can set a top-level style and let it cascade, that works very well and as expected (closer to the View, the modifier takes over). But, there are just some weird use cases where it would be nice to flip the script.

Upvotes: 0

Views: 1250

Answers (1)

rob mayoff
rob mayoff

Reputation: 385590

Generally, buttonStyle propagates to child views, so ideally you would only need to set your “house style” once on the root view of your app.

The well-known place where this fails to work is the presentation modifiers like .sheet, which do not propagate styles to the presented view hierarchy. So you will need to write your own versions of the presentation modifiers that re-apply your house style.

For example, here's a custom ButtonStyle:

struct HouseButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .padding(20)
            .background {
                Capsule(style: .continuous)
                    .foregroundColor(.pink)
            }
            .saturation(configuration.isPressed ? 1 : 0.5)
    }
}

And here's a cover for sheet that applies the custom button style to the presented content:

extension View {
    func houseSheet<Content: View>(
        isPresented: Binding<Bool>,
        onDismiss: (() -> Void)? = nil,
        @ViewBuilder content: @escaping () -> Content
    ) -> some View {
        return sheet(isPresented: isPresented, onDismiss: onDismiss) {
            content()
                .buttonStyle(HouseButtonStyle())
        }
    }
}

We can test out whether a NavigationLink, a sheet, and a houseSheet propagate the button style:

struct ContentView: View {
    @State var showingHouseSheet = false
    @State var showingStandardSheet = false

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink("Navigation Push") {
                    ContentView()
                }

                Button("Standard Sheet") {
                    showingStandardSheet = true
                }

                Button("House Sheet") {
                    showingHouseSheet = true
                }
            }
            .sheet(isPresented: $showingStandardSheet) {
                ContentView()
            }
            .houseSheet(isPresented: $showingHouseSheet) {
                ContentView()
            }
        }
    }
}

Here's the root view that applies the house button style at the highest level:

struct RootView: View {
    var body: some View {
        ContentView()
            .buttonStyle(HouseButtonStyle())
    }
}

If you play with this, you'll find that both NavigationLink and houseSheet propagate the button style to the presented content, but sheet does not.

Upvotes: 1

Related Questions