What causes the view modifier to be used when a view calls its body?

1 week ago 11
ARTICLE AD BOX

randomBackgroundColor always gets called when its enclosing body is evaluated, regardless of the dependencies of the view to which it is applied. After all, randomBackgroundColor is just a regular Swift function. You call it in body, so every time body gets evaluated, a new random color gets created.

In A,

struct A: View { var body: some View { Text("ASD") .padding() .randomBackgroundColor() } }

The text does not change color not because the text does not have dependencies, but because A does not have dependencies.

In B,

struct B: View { @Binding var num: Int var body: some View { HStack { Text("\(num)") .padding() .randomBackgroundColor() // color changes. Correct since its dependency changes C(fixed: "Fixed Text") .randomBackgroundColor() // does not change color. Correct since the text remains the same } } }

Both Text("\(num)") and C change colors when num changes, because num is a dependency of B. Whenever num changes, B.body gets called.

You can't visually see C changing color, because there is an additional randomBackgroundColor in C.body which completely covers up the background you added in B.body. Since C does not have dependencies, C.body is only called once and therefore the only background that you can visually see does not change.

Now the behavior in Test should seem a lot more consistent. Test has vm.num as a dependency, so when vm.num changes, Test.body gets called, and all three .randomBackgroundColor() in it gets called. As a result, Text("ASD"), B, as well as C, all get a new color. Again, the new color that C got is completely covered up by the background added in C.body.


Side note:

Instead of directly applying a new color in randomBackgroundColor, you could extract the implementation into a ViewModifier,

extension View { func randomBackgroundColor() -> some View { modifier(RandomBackgroundColorModifier()) } } struct RandomBackgroundColorModifier: ViewModifier { func body(content: Content) -> some View { content.background { Color(cgColor: .init( red: .random(in: 0...1.0), green: .random(in: 0...1.0), blue: .random(in: 0...1.0), alpha: 1)) } } }

In this case, a new color gets created only when RandomBackgroundColorModifier.body is called. Since RandomBackgroundColorModifier does not have any dependencies, all your views' backgrounds will not change (unless their identity changes).

Read Entire Article