0

I would like to create a view modifier that invokes Image.renderingMode(.template) if some conditions (based on the environment) are met. Unfortunately, .renderingMode is not defined on View, but only on Image, and therefore I cannot invoke it on Content since Content is an opaque type that only conforms to View and therefore only supports functions declared on View.

extension SemanticColor {
    func realizedColor(itemStyle: ItemStyle) -> Color? {
        switch itemStyle {
        case .highlight:
            return .white
        case .warning:
            return .black
        case .default:
            return nil
        }
    }
}

struct SemanticColorViewModifier: ViewModifier {
    
    @Environment(\.itemStyle)
    private var itemStyle: ItemStyle
    
    var semanticColor: SemanticColor
    
    func body(content: Content) -> some View {
        let color: self.semanticColor.realizedColor(itemStyle: self.itemStyle)
        // TODO: apply Image.renderingMode(.template) if color != nil
        return content.foregroundColor(color)
    }
}

public extension View {
    func semanticForegroundColor(_ color: SemanticColor) -> some View {
        return self.modifier(SemanticColorViewModifier(semanticColor: color))
    }
}

I tried to create a custom ImageModifier inspired by this answer from jboi to SwiftUI: ViewModifier where content is an Image, but that did fall short because body is not re-evaluated once the state changes.

My current workaround is to declare a .semanticColor function on View that uses the above ViewModifier sans .renderingMode, and use a nested View that duplicates the logic for Images.

struct SematicallyColoredImage: View {

    init(image: Image, semanticColor: SemanticColor) {
        self.image = image
        self.semanticColor = semanticColor
    }
    
    @Environment(\.itemStyle)
    private var itemStyle: ItemStyle
    
    private let image: Image
    private let semanticColor: SemanticColor
    
    var body: some View {
        if let color = self.semanticColor.realizedColor(itemStyle: self.itemStyle) {
            return AnyView(self.image.renderingMode(.template).foregroundColor(color))
        } else {
            return AnyView(self.image)
        }
    }
}

public extension Image {
    func semanticForegroundColor(_ color: SemanticColor) -> some View {
        return SematicallyColoredImage(image: self, semanticColor: color)
    }
}

This code works, but it feels redundant and problematic: If I invoke .semanticForegroundColor on an Image after another modifier returns some View, then the result does not take modify the rendering mode, whereas it does if I invoke it if the type is still Image.

nd.
  • 8,699
  • 2
  • 32
  • 42

0 Answers0