andrewz
andrewz

Reputation: 5220

How to update a complicated model of a View from within its body

I am building a custom View struct with a large and complicated model that I want to update from within the var body : some View { ... } property (ex. tapping on a button representing a table column in the view should change the sort order of the rows in the table). I am not allowed to modify this model from within body because self is immutable however:

struct MyTable : View {

  struct Configuration {
     // a LOT of data here, ie header, rows, footer, style, etc

     mutating func update() { ... } // this method must be called before view render
  }
  var configuration : Configuration

  var body : some View {
    // build the view here based on configuration
    self.configuration.columns[3].sortAscending = false // error, `self` is immutable
    self.configuration.update() // error, `self` is immutable
  }
}

I really don't want to create @State variables for all of the configuration data because 1) there's a lot of configuration data, 2) it would be difficult to model the model in such a way.

I tried instead making configuration a @State var, but I am unable to set the configuration object at init() time even though the code compiles and runs! (BTW, the configuration var now needs to be initialized before init, otherwise I get an error on the line self.configuration = c that self is used before all members are initialized -- this is likely a complication with using @State which is a property wrapper.)

struct MyTable : View {

  struct Configuration {
     ...
  }

  @State var configuration : Configuration = Configuration() // must initialize the @State var

  init(configuration c: Configuration) {
    self.configuration = c // does not assign `c` to `configuration` !!!
    self.$configuration.wrappedValue = c // this also does not assign !!!
    self.configuration.update()
  }

  var body : some View {
    // build the view here based on configuration
    self.configuration.columns[3].sortAscending = false // ok, no error now about mutating a @State var
    self.configuration.update() // ok, no error
  }
}

Upvotes: 0

Views: 39

Answers (1)

andrewz
andrewz

Reputation: 5220

I came up with a work around where I don't need to call update() in MyTable.init() by creating a custom init() in Configuration that will call update(). This way the init() in MyTable is unnecessary and this approach resolves all previously encountered problems:

struct MyTable : View {

  struct Configuration {
     ...

     init(...) {
         ...
         self.update()
     }

     mutating func update() { ... } // this method must be called before view render
  }

  @State var configuration : Configuration // note that this property can't be marked private because we want to inject `configuration` at init

  var body : some View {
    // build the view here based on configuration
    self.configuration.columns[3].sortAscending = false // ok, no error now about mutating a @State var
    self.configuration.update() // ok, no error
    ...
  }
}

Then in my calling code:

return MyTable.init(configuration: MyTable.Configuration.init(...))

Upvotes: 0

Related Questions