'SwiftUI: Why does @AppStorage work differently to my custom binding?

I have a modal presented Sheet which should display based on a UserDefault bool. I wrote a UI-Test which dismisses the sheet and I use launch arguments to control the defaults value.

However, when I tried using @AppStorage initially it didn't seem to persist the value, or was secretly not writing it? My test failed as after 'dismissing' the modal pops back up as the value is unchanged.

To work around this I wrote a custom binding. But i'm not sure what the behaviour difference is between the two implementations? The test passes this way.

Does anyone know what i'm not understanding sorry?

Cheers!

Simple Example

1. AppStorage

struct ContentView: View {
  @AppStorage("should.show.sheet") private var binding: Bool = true

  var body: some View {
    Text("Content View")
      .sheet(isPresented: $binding) {
        Text("Sheet")
      }
  }
}

2. Custom Binding:

struct ContentView: View {
  var body: some View {
    let binding = Binding<Bool> {
        UserDefaults.standard.bool(forKey: "should.show.sheet")
    } set: {
        UserDefaults.standard.set($0, forKey: "should.show.sheet")
    }
      
    Text("Content View")
      .sheet(isPresented: binding) {
        Text("Sheet")
      }
  }
}

Test Case:

final class SheetUITests: XCTestCase {
  override func setUpWithError() throws {
      continueAfterFailure = false
  }
  
  func testDismiss() {
      // Given 'true' userdefault value to show sheet on launch
      let app = XCUIApplication()
      app.launchArguments += ["-should.show.sheet", "<true/>"]
      app.launch()
      
      // When the user dismisses the modal view
      app.swipeDown()
      
      // Wait a second for animation (make sure it doesn't pop back)
      sleep(1)
      
      // Then the sheet should not be displayed
      XCTAssertFalse(app.staticTexts["Sheet"].exists)
  }
}


Solution 1:[1]

  1. It does not work even when running app, because of that "." in key name (looks like this is AppStorage limitation, so use simple notation, like isSheet.

  2. IMO the test-case is not correct, because it overrides defaults by arguments domain, but it is read-only, so writing is tried into persistent domain, but there might be same value there, so no change (read didSet) event is generated, so there no update in UI.

  3. To test this it should be paired events inside app, ie. one gives AppStorage value true, other one gives false

*Note: boolean value is presented in arguments as 0/1 (not XML), lie -isSheet 1

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Asperi