Search code examples
swiftswiftuiswiftui-charts

On which conditions will view modifier code get called? view modifier isn't rendered


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
  }
}

Solution

  • 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 ViewModifiers 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()))
        }
    }