EN VI

Ios - SwiftUI. Access custom view subviews from custom view modifier?

2024-03-13 01:30:06
How to Ios - SwiftUI. Access custom view subviews from custom view modifier

I have a custom view:

public struct CustomView: View {
    var text1: String
    var text2: String

    public var body: some View {
        VStack {
            Text(text1).tag(0)
            Text(text2).tag(1)
        }
    }
}

I want to create view modifier and pass custom Style parameter to it:

struct Watermark: ViewModifier {
    var style: Style

    func body(content: Content) -> some View {
          if style == .one {
              // access Text with tag 0 and set foreground color to red
              // access Text with tag 1 and set foreground color to blue
          } else if style == .two {
              // access Text with tag 0 and set foreground color to black
              // access Text with tag 1 and set foreground color to pink
          }
}

How to access content child view from Modifier to change it style ?

I would like to do something similar as apple do with SF Image

enter image description here

P.S.: I want to use exactly view modifier. I don't want to pass Style as parameter to view. Because I want to build complex custom views where it can be more then 7 parameters. And I don't want to pass 7 parameters to view initialiser. View modifiers looks more nicer.

Solution:

You should not use tag for identifying views. tag is very hard to read. The view trait key for it is an internal type, so you'd need to use Mirror.

You can use the environment instead. Create an environment key for each of the 7+ parameters you will have.

In this case, there are 2 colors, so I'll create 2 keys. If there are more colors, I'd instead use one key that has an array as a value.

struct Color1Key: EnvironmentKey {
    static var defaultValue = AnyShapeStyle(.primary)
}

struct Color2Key: EnvironmentKey {
    static var defaultValue = AnyShapeStyle(.secondary)
}

extension EnvironmentValues {
    var color1: AnyShapeStyle {
        get { self[Color1Key.self] }
        set { self[Color1Key.self] = newValue }
    }
    var color2: AnyShapeStyle {
        get { self[Color2Key.self] }
        set { self[Color2Key.self] = newValue }
    }
}

Instead of tag, write your own view modifiers that read from the environment.

struct Color1Modifier: ViewModifier {
    @Environment(\.color1) var color1
    
    func body(content: Content) -> some View {
        content.foregroundStyle(color1)
    }
}

struct Color2Modifier: ViewModifier {
    @Environment(\.color2) var color2
    
    func body(content: Content) -> some View {
        content.foregroundStyle(color2)
    }
}

extension View {
    func color1() -> some View {
        modifier(Color1Modifier())
    }
    func color2() -> some View {
        modifier(Color2Modifier())
    }
}

// usage:
VStack {
    Text(text1).color1()
    Text(text2).color2()
}
.modifier(Watermark(style: ...))

Then Watermark can write to the environment:

func body(content: Content) -> some View {
    content
        .environment(\.color1, AnyShapeStyle(style == .one ? .red : .black))
        .environment(\.color2, AnyShapeStyle(style == .one ? .blue : .pink))
}

Here is an implementation of the same idea but with one environment key holding an array of color styles:

struct CustomView: View {
    var text1: String
    var text2: String
    
    public var body: some View {
        VStack {
            Text(text1).color(0)
            Text(text2).color(1)
        }
        .modifier(Watermark(style: ...))
    }
}

struct ColorsModifier: ViewModifier {
    let index: Int
    
    @Environment(\.colors) var colors
    
    func body(content: Content) -> some View {
        content.foregroundStyle(
            colors.indices.contains(index) ? colors[index] : AnyShapeStyle(.opacity(1))
        )
    }
}

extension View {
    func color(_ n: Int) -> some View {
        modifier(ColorsModifier(index: n))
    }
}

struct ColorsKey: EnvironmentKey {
    static var defaultValue: [AnyShapeStyle] = []
}

extension EnvironmentValues {
    var colors: [AnyShapeStyle] {
        get { self[ColorsKey.self] }
        set { self[ColorsKey.self] = newValue }
    }
}

struct Watermark: ViewModifier {
    enum Style {
        case one, two
    }
    
    var style: Style
    
    func body(content: Content) -> some View {
        content
            .environment(
                \.colors,
                style == .one ?
                 [.init(.red), .init(.blue)] :
                 [.init(.black), .init(.pink)]
            )
    }
}
Answer

Login


Forgot Your Password?

Create Account


Lost your password? Please enter your email address. You will receive a link to create a new password.

Reset Password

Back to login