Style app in SwiftUI
In good old UIKit times, when I worked on the white label app News, it was very helpful to separate app views/controllers from app stylings like colors and fonts. Nowadays, in the SwiftUI century, I tried to apply the same pattern.
Our first task is to define AppStyleProtocol
, this usually looks the same as in UIKit.
protocol AppStyleProtocol {
var colors: AppColorsProtocol { get }
var fonts: AppFontsProtocol { get }
}
protocol AppColorsProtocol {
var text: Color { get }
var background: Color { get }
}
protocol AppFontsProtocol {
var title: Font { get }
}
The second step, the same as in UIKit, is to create some implementation of AppStyleProtocol
.
struct LimeAppStyle: AppStyleProtocol {
let colors: AppColorsProtocol = LimeAppColors()
let fonts: AppFontsProtocol = AppFonts()
}
struct LimeAppColors: AppColorsProtocol {
let text = Color.red
let background = Color.green
}
struct AppFonts: AppFontsProtocol {
let title = Font.headline
}
Tip: In SwiftUI world to make things easier we can inject AppStyleProtocol
implementation to every SwiftUI View like environment variable. I define a new environment style
property for that. This environment property can be set only on the first View and then is injected to every SwiftUI View.
struct AppStyleKey: EnvironmentKey {
static let defaultValue: AppStyleProtocol = AppStyle()
}
extension EnvironmentValues {
var style: AppStyleProtocol {
get { self[AppStyleKey.self] }
set { self[AppStyleKey.self] = newValue }
}
}
Tip: By using a View
and Text
extensions we can make our implementation more clear and powerful.
extension View {
func style(_ style: AppStyleProtocol) -> some View {
self
.background(style.colors.background)
}
}
extension Text {
func title(_ style: AppStyleProtocol) -> some View {
self
.foregroundColor(style.colors.text)
.font(style.fonts.title)
}
}
And finally can implement some view and easily style without define any specific color, font or what you want in your custome View.
struct SampleView: View {
@Environment(\.style) var style: AppStyleProtocol
var body: some View {
VStack {
Text("Fruits!")
.title(style)
}
.frame(width: 300, height: 300)
.style(style)
}
}
Same View with different AppStyle - default
lime
and orange
then can looks like this:
Download code example with LimeAppStyle and OrangeAppStyle.