Why isn't a closure in @State array property triggering state change?

(I've posted this originally on StackOverflow and answered it myself, but I don't have enough experience with Swift and SwiftUI and would like this to be review by someone more experienced, to avoid misleading other users [https://stackoverflow.com/questions/61903284/why-isnt-a-closure-in-state-array-property-triggering-state-change])

A

test
view has @State
showTitle
,
title
and
items
where the
title
value text is controlled by a closure assigned to a CTA
show title
.


When the

showTitle
state changes, the value presented in the body Content of
test
view changes accordingly:


  
 
Text({ self.showTitle ? "Yes, showTitle!" : "No, showTitle!" }())


While the case where the

closure
is a value in the array
items
does not change. Why isn't the closure triggering the title state?


  
 
NestedView(title: $0.title())

The complete source-code:

struct test: View {
  @State var showTitle: Bool = true
  @State var title: String
  @State var items: [Foobar]

  var body: some View {
  VStack {
  Group {
  Text("Case 1")
  Text({ self.showTitle ? "Yes, showTitle!" : "No, showTitle!" }())
  }
  Group {
  Text("Case 2")
  ForEach (self.items, id: \.id) {
  NestedView(title: $0.title())
  }
  }
  Button("show title") {
  print("show title cb")
  self.showTitle.toggle()
  }
  }.onAppear {
  let data = ["hello", "world", "test"]
  for title in data {
  self.items.append(Foobar(title: { self.showTitle ? title : "n/a" }))
  }
  }
  }
}

struct NestedView: View {
  var title: String
  var body: some View {
  Text("\(title)")
  }
}


What's expected is that "Case 2" to have a similar side-effect we have in "Case 1" that should display "n/a" on

showTitle
toggle.

Output demo:
https://i.stack.imgur.com/YJVQu.gif

My conclusion:

From what I understand, the reason why the initial code does not work is related to the

showTitle
property that is passed to the Array and holds a copy of the
value
(creates a unique copy of the data).


I did think @State would make it controllable and mutable, and the

closure
would capture and store the reference (create a shared instance). In other words, to have had a
reference
, instead of a copied
value
! Feel free to correct me, if that's not the case, but that's what it looks like based on my analysis.


With that being said, I kept the initial thought process, I still want to pass a

closure
to the Array and have the state changes propagated, cause side-effects, accordingly to any references to it!


So, I've used the same pattern but instead of relying on a primitive type for

showTitle
Bool
, created a
Class
that conforms to the protocol
ObservableObject
: since
Classes
are
reference types
.


So, let's have a look and see how this worked out:


import SwiftUI

class MyOption: ObservableObject {
  @Published var option: Bool = false  
}

struct Foobar: Identifiable {
  var id: UUID = UUID()
  var title: () -> String

  init (title: @escaping () -> String) {
  self.title = title
  }
}

struct test: View {
  @EnvironmentObject var showTitle: MyOption
  @State var title: String
  @State var items: [Foobar]

  var body: some View {
  VStack {
  Group {
  Text("Case 1")
  Text(self.showTitle.option ? "Yes, showTitle!" : "No, showTitle!")
  }
  Group {
  Text("Case 2")
  ForEach (self.items, id: \.id) {
  NestedView(title: $0.title())
  }
  }
  Button("show title") {
  print("show title cb")
  self.showTitle.option.toggle()
  print("self.showTitle.option: ", self.showTitle.option)
  }
  }.onAppear {
  let data = ["hello", "world", "test"]
  for title in data {
  self.items.append(Foobar(title: { self.showTitle.option ? title : "n/a" }))
  }
  }
  }
}

struct NestedView: View {
  var title: String
  var body: some View {
  Text("\(title)")
  }
}


The result as expected: https://i.stack.imgur.com/dAHjh.gif

Please share your knowledge,
Thank you!

Replies

Is the question about array not updating ?


Maybe you could catch some idea here:

https://forums.developer.apple.com/thread/132750

No, the question is on the title. It's about closures.

That was my point: array not updating (I mean not causing update).


What do you mean by CTA ?

Which closure do you refer to in the first code fragment ?

The CTA means call to action, and it's demonstrated in the GIF animation, which is easier to see.
Line 15:
NestedView(title: $0.title())
The source code is available in the initial post and the whole flow is better described there too.