I'm trying to define a SwiftUI view as a reusable component in my app. Here is how it is defined:
struct CustomContainer: View {
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 8, style: .circular)
.stroke(Color.gray, lineWidth: 1.2)
// foreground here ...
}
}
}
I would like for users of my code to be able to apply the view modifier stroke(_:lineWidth:)
on CustomContainer
, but I would like the CustomContainer
to internally apply that modifier on the rounded rectangle inside its body
. How would I do that?
This is my desired use of CustomContainer
:
struct FormView: View {
var body: some View {
CustomContainer()
.stroke(Color.orange, lineWidth: 2)
}
}
I was able to do this using environment variables. I was initially confused because I found a StackOverflow answer that recommended the use of PreferenceKey
, but the Apple Docs say that preference keys are for propagating values from a child view to its parent, while what I want to do is for a parent view to propagate a value down to a child, which is what environment values are for.
I added the following to my custom container:
struct CustomContainer: View {
// ...
// Define a new environment key.
struct BorderStyle: EnvironmentKey {
let color: Color
let width: CGFloat
static let defaultValue = BorderStyle(color: Color.gray, width: 1.2)
}
// Convenience view modifier for supplying values to the environment key.
func border(_ color: Color, width: CGFloat = 1.2) -> some View {
return environment(\.containerBorderStyle, BorderStyle(color: color, width: width))
}
// Capture the environment key in a property of the parent view.
@Environment(\.containerBorderStyle) private var borderStyle: BorderStyle
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 8, style: .circular)
.stroke(borderStyle.color, lineWidth: borderStyle.width) // use the environment key here
// foreground here ...
}
}
}
And then I declared the custom environment key to an EnvironmentValues
extension:
extension EnvironmentValues {
var containerBorderStyle: CustomContainer.BorderStyle {
get { self[CustomContainer.BorderStyle.self] }
set { self[CustomContainer.BorderStyle.self] = newValue }
}
}