Is there a way to create a modifier to update a @State private var in the view being modified?
I have a custom view that returns either a Text with a "dynamic" background color OR a Circle with a "dynamic" foreground color.
struct ChildView: View {
var theText = ""
@State private var color = Color(.purple)
var body: some View {
HStack {
if theText.isEmpty { // If there's no theText, a Circle is created
Circle()
.foregroundColor(color)
.frame(width: 100, height: 100)
} else { // If theText is provided, a Text is created
Text(theText)
.padding()
.background(RoundedRectangle(cornerRadius: 25.0)
.foregroundColor(color))
.foregroundColor(.white)
}
}
}
}
I re-use this view in different parts around my app. As you can see, the only parameter I need to specify is theText. So, the possible ways to create this ChildView are as follows:
struct SomeParentView: View {
var body: some View {
VStack(spacing: 20) {
ChildView() // <- Will create a circle
ChildView(theText: "Hello world!") // <- Will create a text with background
}
}
}

Nothing fancy so far. Now, what I need is to create (maybe) a modifier or the like so that in the parent views I can change the value of that @State private var color from .red to other color if I need more customization on that ChildView. Example of what I'm trying to achieve:
struct SomeOtherParentView: View {
var body: some View {
HStack(spacing: 20) {
ChildView()
ChildView(theText: "Hello world!")
.someModifierOrTheLike(color: Color.green) // <- what I think I need
}
}
}
I know I could just remove the private keyword from that var and pass the color as parameter in the constructor (ex: ChildView(theText: "Hello World", color: .green)), but I don't think that's the way to solve this problem, because if I need more customization on the child view I'd end up with a very large constructor.
So, Any ideas on how to achieve what I'm looking for? Hope I explained myself :) Thanks!!!
It is your view and modifiers are just functions that generate another, modified, view, so... here is some possible simple way to achieve what you want.
Tested with Xcode 12 / iOS 14

struct ChildView: View {
var theText = ""
@State private var color = Color(.purple)
var body: some View {
HStack {
if theText.isEmpty { // If there's no theText, a Circle is created
Circle()
.foregroundColor(color)
.frame(width: 100, height: 100)
} else { // If theText is provided, a Text is created
Text(theText)
.padding()
.background(RoundedRectangle(cornerRadius: 25.0)
.foregroundColor(color))
.foregroundColor(.white)
}
}
}
// simply modify self, as self is just a value
public func someModifierOrTheLike(color: Color) -> some View {
var view = self
view._color = State(initialValue: color)
return view.id(UUID())
}
}
Using a custom ViewModifier is indeed a way to help expose a simpler interface to users, but the general idea of how to pass customization parameters to a View (other than using an init), is via environment variables with .environment.
struct MyColorKey: EnvironmentKey {
static var defaultValue: Color = .black
}
extension EnvironmentValues {
var myColor: Color {
get { self[MyColorKey] }
set { self[MyColorKey] = newValue }
}
}
Then you could rely on this in your View:
struct ChildView: View {
@Environment(\.myColor) var color: Color
var body: some View {
Circle()
.foregroundColor(color)
.frame(width: 100, height: 100)
}
}
And the usage would be:
ChildView()
.environment(\.myColor, .blue)
You can make it somewhat nicer by using a view modifier:
struct MyColorModifier: ViewModifier {
var color: Color
func body(content: Content) -> some View {
content
.environment(\.myColor, color)
}
}
extension ChildView {
func myColor(_ color: Color) {
self.modifier(MyColorModifier(color: color)
}
}
ChildView()
.myColor(.blue)
Of course, if you have multiple customizations settings or if this is too low-level for the user, you could create a ViewModifier that exposes a subset of them, or create a type that encapsulates a style, like SwiftUI does with a .buttonStyle(_:)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With