3

I am kind of a SwiftUI newbe but my question is essentially this:
I have a view that looks like this:

struct myView: View {
    var label = Text("label")
    var subLabel = Text("sublabel")

    var body: some View {
        VStack {
            label
            subLabel
        }
    }

    public func primaryColor(color: Color) -> some View {
        var view = self
        view.label = view.label.foregroundColor(color)
        return view.id(UUID())
    }

    public func secondaryColor(color: Color) -> some View {
        var view = self
        view.subLabel = view.subLabel.foregroundColor(color)
        return view.id(UUID())
    }
}

And here is my problem:
In my parent view, I would like to call myView as follows

struct parentView: View {
    var body: some View {
        myView()
            .primaryColor(color: .red)
            .secondaryColor(color: .blue)
    }
}

Using only one of these modifiers works fine but stacking them won't work (since they return some View ?).
I don't think that I can use standard modifiers since I have to access myView variables, which (I think) wouldn't be possible by using a ViewModifier.
Is there any way to achieve my goal or am I going on the wrong direction ?

Paul
  • 35
  • 7

2 Answers2

2

Here is a solution for you - remove .id (it is really not needed, result will be a copy anyway):

struct myView: View {
    var label = Text("label")
    var subLabel = Text("sublabel")

    var body: some View {
        VStack {
            label
            subLabel
        }
    }

    public func primaryColor(color: Color) -> Self {
        var view = self
        view.label = view.label.foregroundColor(color)
        return view
    }

    public func secondaryColor(color: Color) -> Self {
        var view = self
        view.subLabel = view.subLabel.foregroundColor(color)
        return view
    }
}

and no changes in parent view

Demo prepared with Xcode 13 / iOS 15

demo

Asperi
  • 228,894
  • 20
  • 464
  • 690
0

I'd suggest a refactor where primaryColor and secondaryColor can be passed as optional parameters to your MyView (in Swift, it is common practice to capitalize type names):

struct MyView: View {
    var primaryColor : Color = .primary
    var secondaryColor : Color = .secondary
    
    var body: some View {
        VStack {
            Text("label")
                .foregroundColor(primaryColor)
            Text("sublabel")
                .foregroundColor(secondaryColor)
        }
    }
}

struct ParentView: View {
    var body: some View {
        MyView(primaryColor: .red, secondaryColor: .blue)
    }
}

This way, the code is much shorter and more simple, and follows the general form/practices of SwiftUI.

You could also use Environment keys/values to pass the properties down, but this takes more code (shown here for just the primary color, but you could expand it to secondary as well):


private struct PrimaryColorKey: EnvironmentKey {
    static let defaultValue: Color = .primary
}

extension EnvironmentValues {
    var primaryColor: Color {
        get { self[PrimaryColorKey.self] }
        set { self[PrimaryColorKey.self] = newValue }
    }
}

extension View {
    func primaryColor(_ primary: Color) -> some View {
        environment(\.primaryColor, primary)
    }
}

struct MyView: View {
    @Environment(\.primaryColor) var primaryColor : Color
    
    var body: some View {
        VStack {
            Text("label")
                .foregroundColor(primaryColor)
        }
    }
}

struct ParentView: View {
    var body: some View {
        MyView()
            .primaryColor(.red)
    }
}
jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • 1
    I had forgotten about `Environment` values, I'll try this out and eventually accept your reply. As of passing them as optional params, I wanted to avoid that for readability since my view already have about 7 params and I want to be able to customize other properties (fonts, ...) – Paul Sep 05 '21 at 17:13