Ulrich
Ulrich

Reputation: 225

SwiftUI - @State property is not updated

I experience some weird behavior since updating to Xcode 12 / iOS 14. To verify this behavior, I isolated it in a test app.

In my original app, I have multiple Sheets to control. This is why I have en enum to differentiate between the currently active sheet and I store this in a @State private var activeSheet. In the @ViewBuilder function sheetContent() I return the View for the selected Sheet.

For this test app, I only implemented one single sheet for simplicity reasons.

Here's the ContentView's code:

struct ContentView: View {
    @State private var showingSheet = false
    @State private var activeSheet = ContentViewSheets.State.none
    var body: some View {
        Button(action: {
            activeSheet = .aSheet
            showingSheet = true     // <-- ISSUE: activeSheet is still set to .none here!
        }) {
            Text("Open Sheet")
        }
        .sheet(isPresented: $showingSheet, content: sheetContent)
    }
    
    @ViewBuilder
    private func sheetContent() -> some View {
        switch activeSheet {
        case .aSheet:
            Text("I'm the right sheet!")
        case .none:
            Text("Oops! I'm not supposed to show up!")
        }
    }
}

This is the code for the enum:

class ContentViewSheets {
    enum State {
        case aSheet
        case none
    }
}

As commented in the ContentView code above, the value of the activeSheet property is never changed to .aSheet, but remains .none - so that the wrong sheet will be presented.

Is this a bug or didn't I properly understand Apple's @State? They just write in the documentation that it shall not be used in initializer, but only within the body, which I definitely do.

Upvotes: 4

Views: 517

Answers (1)

Asperi
Asperi

Reputation: 258345

Instead of using two states, use one and explicitly designed sheet modifier for such scenarios.

Tested with Xcode 12 / iOS 14

class ContentViewSheets {
    enum State: Identifiable {
        case aSheet
        case none

        var id: State { self }
    }
}

struct ContentView: View {
    @State private var activeSheet: ContentViewSheets.State?
    var body: some View {
        Button(action: {
            activeSheet = .aSheet
        }) {
            Text("Open Sheet")
        }
        .sheet(item: $activeSheet) { 
           sheetContent($0) // << activate state passed here !!!
        }
    }

    @ViewBuilder
    private func sheetContent(_ state: ContentViewSheets.State) -> some View {
            switch state {
            case .aSheet:
                Text("I'm the right sheet!")
            default:
                Text("Oops! I'm not supposed to show up!")
            }
    }
}

Upvotes: 5

Related Questions