iosswiftuiuiappearancetheming

Is it to automatically apply themes to SwiftUI controls as with Subclasses+Protocols+Appearance in UIKit?


After working with UIKit for quite a while I am now making my first steps in SwiftUI. In UIKit it is quite easy apply different themes to an app (e.g. as described here):

  1. Create different subclasses for controls which should have different styles. E.g. class HeadlineLabel: UILabel {} and class BodyLabel: UILabel {}
  2. Define a Theme protocol which controls the different styling options like different fonts + text sizes for the different label types.
  3. Create a ThemeManager which applies the styles of the current Theme to the controls using the appearance proxy. E.g. HeadlineLabel.appearance().textColor = theme.headlineColor

The big advantage of this approach is, that in the rest of the code one has not think about theming at all. All one has to do, is to use the correct control subclasses (HeadlineLabel, etc.) within the XIB or Storyboard files.

Is this also possible when working with SwiftUI?


Of course it is no problem to create and use a theme protocol when working with SwiftUI. However, since a SwiftUI view is a struct, one cannot create custom sub classes like struct HeadlineText: Text or struct BodyText: Text, simply because structs do not have inheritance...

So the only solution I found, is to apply the theming options to all controls manuall:

...
Text("Headline 1")
    .foregroundColor(ThemeManager.currentTheme.headlineColor)
Text("Body 1")
    .foregroundColor(ThemeManager.currentTheme.bodyColor)

Text("Headline 2")
    .foregroundColor(ThemeManager.currentTheme.headlineColor)
Text("Body 2")
    .foregroundColor(ThemeManager.currentTheme.bodyColor)

// Not possible
HeadlineText("Headline 3)
BodyText("Body 3")

Is this correct? Or is possible to apply styles to SwiftUI controls of a common type automatically?


Solution

  • It's possible. You don't subclass Text, you subclass View to create a custom container:

    struct HeadlineText: View {
        var text: String
        init(_ text: String) {
            self.text = text
        }
        var body: some View {
            Text(text)
            //customize
        }
    }
    
    HealineText("Hello")
    

    Or you can create a custom ViewMofifier:

    struct HeadlineText: ViewModifier {
        func body(content: Content) -> some View {
            content
            //customize
        }
    }
    
    extension View {//or extend Text
        func headlineText() -> some View {
            return self
                .modifier(HeadlineText)
        }
    }
    
    Text("")
        .headlineText()