I have a view modifier which sets the xdomain of a chart. I had this code littered all over the place (multiple charts), so instead tried to consolidate the code.
For some reason it doesn't get called when the chart is rendered when the code is part of the view modifier.
functioning (scattered to each chart) code which updates when chart is updated:
struct MyChart: View {
var body: some View {
Chart {
.... chart stuff...
}
.chartXScale(domain: xdomain())
}
// this will be called time the chart is rendered
func xdomain() -> ClosedRange<Date> {
let now = Date()
let lower = now.addingTimeInterval(-180)
let x = lower...now
return x
}
}
No functioning - The chart domain isn't udpated each time the chart updates - only the first time does it get set
struct DomainModifer: ViewModifier {
func body(content: Content) -> some View {
content.chartXScale(domain: xdomain())
}
}
extension View {
func xdomain() -> some View {
modifier(DomainModifer())
}
}
func xdomain() -> ClosedRange<Date> {
let now = Date()
let lower = now.addingTimeInterval(-180)
let x = lower...now
return x
}
struct MyChart: View {
var body: some View {
Chart {
.... chart stuff...
}
.xdomain() // doesn't update
}
}
As you may know, SwiftUI would run body
to find the dependencies (e.g. @State
, @Binding
, @ObservedObject
) of a view. When any of the dependencies change, body
is run again to find out what has changed and SwiftUI updates the view according to that. The same applies to ViewModifier
s too, with body(content:)
.
SwiftUI doesn't treat Date()
as a "dependency". It doesn't make sense for SwiftUI tried to update the view every time Date()
changes, after all. When the chart is updated, SwiftUI sees that none of the dependencies of DomainModifer
has changed (it literally has no dependencies!), so it doesn't call body(content:)
.
The first code snippet works because when the chart updates, MyChart.body
is executed, and in there you call xdomain()
, so xdomain()
is called for every update to MyChart
.
The simplest way to fix this would be to not use a ViewModifier
. Just put all the code in the View
extension:
extension View {
// the View extension method and the method that generates a range
// should have different names
func xDomain() -> some View {
chartXScale(xdomain())
}
}
Now xdomain()
will be called whenever you call xDomain()
, which you do in body
.
You can also keep using the ViewModifier
, but the ClosedRange<Date>
should be passed from the View
extension.
extension View {
// the View extension method and the method that generates a range
// should have different names
func xDomain() -> some View {
modifier(DomainModifer(domain: xdomain()))
}
}