@State not updating when set via .init([...]) parameter

Hi,

it seems that a @State variable in a View will not update (initialize to the new value) on subsequent calls to the Views .init method.

In the example below (tested on macOS) the Text("internal: \(_internalNumber)") still shows 41, even after 1s is over and number was set to 42 (in the .task).

TestView.init runs as expected and _internalNumber shows the correct value there, but in the body method (on the redraw) _internalNumber still has the old value.

This is not how it should be, right?

import SwiftUI

struct ContentView: View {
  @State private var number = 41

  var body: some View {
    VStack {
      TestView(number: number)
        .fixedSize()
    }
    .task {
      try! await Task.sleep(nanoseconds: 1_000_000_000)
      await MainActor.run {
        print("adjusting")
        number = 42
      }
    }
  }
}

////////////////////////

// MARK: - TestView -

public struct TestView: View {
  public init(number: Int) {
    self.number = number
    
    __internalNumber = .init(initialValue: number)

    print("Init number: \(self.number)")
    print("Init _internalNumber: \(_internalNumber)")
  }
  
  var number: Int
  @State private var _internalNumber: Int

  public var body: some View {
    VStack(alignment: .leading) {
      Text("number: \(number)")
      Text("internal: \(_internalNumber)")
    }
    .debugAction {
      Self._printChanges()
      print("number: \(number)")
      print("_internalNumber: \(_internalNumber)")
    }
  }
}


// MARK: - debugAction

extension View {
  func debugAction(_ closure: () -> Void) -> Self {
    closure()
    return self
  }
}

But if you change __internalNumber from @State to @Binding it will bind to any changes as a result of any external @State side effects.

public struct TestView: View {
    var number: Int
    @Binding private var _internalNumber: Int
    public init(number: Int) {
        self.number = number
        self.__internalNumber = Binding.constant(number) // bind __internalNumber to changes of the external @State variable
    }

  public var body: some View {
    VStack(alignment: .leading) {
      Text("number: \(number)")
      Text("internal: \(_internalNumber)")
    }
    .debugAction {
      Self._printChanges()
      print("number: \(number)")
      print("_internalNumber: \(_internalNumber)")
    }
  }
}

Unfortunately @MobileTen's suggestion with the @Binding does not work in my case, since I need to modify _internalNumber from within TestView as well. This was not mentioned in my problem description above.

The problem case here really is to have an internal @State initialized from an external value and being able to change that "State". Once the "outside" number changes (in ContentView, I would expect TestView.init to be called again (which actually happens), TestView.body to be called again (which also happens) and the changed number to be displayed (which does not happen ;-))

Hi milutz,

I think that perhaps, because @State is not explicitly detected in any View, it never invalidates and refreshes. So, by adding .id() to TestView, it can force and tell that it is not the same View anymore.

Hoping that this really helps you,


struct ContentView: View {
 @State private var number = 41

 var body: some View {
  VStack {
   TestView(number: number)
    .fixedSize()
  }.id(number)
  .task {
   try! await Task.sleep(nanoseconds: 1_000_000_000)
   await MainActor.run {
    print("adjusting")
    number = 42
   }
  }
 }
}

////////////////////////

// MARK: - TestView -

public struct TestView: View {
 public init(number: Int) {
  self.number = number
   
  __internalNumber = .init(initialValue: number)

  print("Init number: \(self.number)")
  print("Init _internalNumber: \(_internalNumber)")
 }
  
 var number: Int
 @State private var _internalNumber: Int

 public var body: some View {
  VStack(alignment: .leading) {
   Text("number: \(number)")
   Text("internal: \(_internalNumber)")
  }
  .debugAction {
   Self._printChanges()
   print("number: \(number)")
   print("_internalNumber: \(_internalNumber)")
  }
 }
}


// MARK: - debugAction

extension View {
 func debugAction(_ closure: () -> Void) -> Self {
  closure()
  return self
 }
}
@State not updating when set via .init([...]) parameter
 
 
Q