Search code examples
iosswiftswiftui

Changing @State variable does not update the View in SwiftUI


I have a following View (took out irrelevant parts):

struct Chart : View {
    var xValues: [String]
    var yValues: [Double]
    @State private var showXValues: Bool = false

    var body = some View {
        ...
        if showXValues {
            ...
        } else {
            ...
        }
        ...
    }
}

then I wanted to add a way to modify this value from outside, so I added a function:

func showXValues(show: Bool) -> Chart {
    self.showXValues = show
    return self
}

so I build the Chart view from the outside like this:

Chart(xValues: ["a", "b", "c"], yValues: [1, 2, 3])
    .showXValues(true)

but it works as if the value was still false. What am I doing wrong? I thought updating an @State variable should update the view. I am pretty new to Swift in general, more so to SwiftUI, am I missing some kind of special technique that should be used here?


Solution

  • There is no need to create func-s. All I have to do is not mark the properties as private but give them an initial value, so they're gonna become optional in the constructor. So user can either specify them, or not care. Like this:

    var showXLabels: Bool = false

    This way the constructor is either Chart(xLabels:yLabels) or Chart(xLabels:yLabels:showXLabels).

    Question had nothing to do with @State.

    Edit, a few years later

    Actually, there is an even cleaner way to solve this for binary options, like show/hide stuff. For more than 2-way configurations, distinct arguments are still the way.

    The point is that OptionSets are intended for exactly this use case, and they can be found throughout Apple frameworks like Foundation etc.

    So, we are just gonna add a Chart.Options struct:

    extension Chart {
        struct Options: OptionSet {
            let rawValue: Int
    
            static var showXValueLabels = Self(rawValue: 1 << 0)
            static var showYValueLabels = Self(rawValue: 1 << 1)
            [... add more options if you want]
        }
    }
    

    Then, the View itself remains more compact, because it will only have a single variable for binary settings:

    struct Chart: View {
        var xValues: [String]
        var yValues: [Int]
        var options: Chart.Options = []
    
        var body: some View {
            VStack {
                Text("A chart")
    
                if options.contains(.showXValueLabels) {
                    [... whatever xValue label specific view]
                }
    
                if options.contains(.showYValueLabels) {
                    [... whatever yValue label specific view]
                }
            }
        }
    }
    

    And you can construct a Chart like this:

    Chart(xValues: ["a", "b", "c"],
          yValues: [1, 2, 3],
          options: [.showXValueLabels, .showYValueLabels])
    

    or

    Chart(xValues: ["a", "b", "c"],
          yValues: [1, 2, 3],
          options: [.showXValueLabels])
    

    or just use the default options:

    Chart(xValues: ["a", "b", "c"],
          yValues: [1, 2, 3])