System views share their styles through the environment: one .buttonStyle or .listStyle
on the root view styles the whole app, and any subtree can override it. Custom views get the
same power by storing a style struct in a custom EnvironmentKey.
Extract a Style Struct
Move presentation parameters out of the view's initializer into one struct with defaults, so data and appearance stop mixing in every call site.
public struct ChartStyle {
let showAxis: Bool
let showLabels: Bool
let labelCount: Int?
let showLegends: Bool
public init(showAxis: Bool = true,
showLabels: Bool = true,
labelCount: Int? = nil,
showLegends: Bool = true) {
self.showAxis = showAxis
self.showLabels = showLabels
self.labelCount = labelCount
self.showLegends = showLegends
}
}
Put the Style in the Environment
An EnvironmentKey holds the default, an EnvironmentValues accessor exposes it, and a
View extension gives callers the same modifier ergonomics as the built-in styles.
struct ChartStyleEnvironmentKey: EnvironmentKey {
static var defaultValue: ChartStyle = .init()
}
extension EnvironmentValues {
var chartStyle: ChartStyle {
get { self[ChartStyleEnvironmentKey.self] }
set { self[ChartStyleEnvironmentKey.self] = newValue }
}
}
extension View {
func chartStyle(_ style: ChartStyle) -> some View {
environment(\.chartStyle, style)
}
}
Read It in the View
The view reads the style with @Environment instead of taking it as a parameter. One
modifier on a container now styles every chart inside it, and a subtree can still override.
public struct BarChartView: View {
@Environment(\.chartStyle) var chartStyle
let dataPoints: [DataPoint]
var body: some View {
// draw bars
if chartStyle.showLabels { /* labels */ }
if chartStyle.showLegends { /* legend */ }
}
}
HStack {
BarChartView(dataPoints: dataPoints1)
BarChartView(dataPoints: dataPoints2)
}
.chartStyle(ChartStyle(showAxis: false, labelCount: 3))
This mirrors how ButtonStyle works: define makeBody(configuration:) in a struct, apply it
once at the root, and every descendant picks it up.