Home > Blockchain >  @State property value is not retain when called within the sheet(item:) method
@State property value is not retain when called within the sheet(item:) method

Time:11-04

In the following code, I have an optional @State variable call isReseting which I set to true with a tap of a button. The button also triggers a sheet when it's tapped, which is working fine but what doesn't work is that if I read the value of the isResting variable inside the .sheet it outputs as nil and I'm expecting that to be true since it was set to true when the button was tapped.

Any idea why the value of the isReseting variable becomes nil inside the .sheet?

struct Item: Identifiable{
    var id = UUID()
    var name:String
    var active:Bool
}

struct ContentView: View {
    
    var items:[Item] = [Item(name: "Oranges", active: true),
                    Item(name: "Apples", active: false),
                    Item(name: "Cookies", active: false) ]
    
    @State private var selectedItem: Item?
    @State private var isReseting: Bool?
        
    var body: some View {
        List{
            ForEach(items){ item in
                HStack{
                    Text(item.name)
                    
                    Button(item.active ? "Reset": "Initiate"){
                        selectedItem = item
                        isReseting = true
                        let _ = print("Value after button tap: \(isReseting)")// output: Value after button tap: Optional(true)
                    }
                    .padding()
                    .background(item.active ? Color.blue: Color.gray)
                    .foregroundColor(.white)
                    .cornerRadius(30)
                    .frame(width: 100, height: 65)
                }
            }
        }
        .sheet(item: $selectedItem){ item in
            // I'm expecting isReseting to be true here, but it's nil
            let _ = print("Value in sheet: \(isReseting)")// outputs: Value in sheet: nil
            
            if isReseting == true {
                Text("It's reseting!!!")
            }else{
                Text("It's NOT reseting")
            }
        }
    }
}

CodePudding user response:

I think you're getting caught by the fact that sheet(item:) only re-renders based on item -- not the encapsulating View's @State variables. As long as you explicitly pass isReseting through the item, it will work:


struct Item: Identifiable, Equatable {
    var id = UUID()
    var name:String
    var active:Bool
}

struct ContentView: View {
    
    var items:[Item] = [Item(name: "Oranges", active: true),
                    Item(name: "Apples", active: false),
                    Item(name: "Cookies", active: false) ]
    
    struct SheetItem : Identifiable {
        var item: Item
        var isReseting: Bool
        
        var id: UUID {
            self.item.id
        }
    }
    
    @State private var selectedItem: SheetItem?
    @State private var isReseting: Bool?
        
    var body: some View {
        List{
            ForEach(items){ item in
                HStack{
                    Text(item.name)
                    
                    Button(item.active ? "Reset": "Initiate"){
                        isReseting = true
                        selectedItem = SheetItem(item: item, isReseting: isReseting ?? false)
                    }
                    .padding()
                    .background(item.active ? Color.blue: Color.gray)
                    .foregroundColor(.white)
                    .cornerRadius(30)
                    .frame(width: 100, height: 65)
                }
            }
        }
        .sheet(item: $selectedItem) { item in
            let _ = print("Value in sheet: \(item.isReseting)")
            
            if item.isReseting == true {
                Text("It's reseting!!!")
            } else {
                Text("It's NOT reseting")
            }
        }
    }
}

A second way to accomplish this is explicitly pass a Binding (even though you don't need to mutate it in the sheet), since it will maintain its link to the @State variable:

struct Item: Identifiable{
    var id = UUID()
    var name:String
    var active:Bool
}

struct ContentView: View {
    
    var items:[Item] = [Item(name: "Oranges", active: true),
                    Item(name: "Apples", active: false),
                    Item(name: "Cookies", active: false) ]
    
    @State private var selectedItem: Item?
    @State private var isReseting: Bool?
        
    var body: some View {
        List{
            ForEach(items){ item in
                HStack{
                    Text(item.name)
                    
                    Button(item.active ? "Reset": "Initiate"){
                        selectedItem = item
                        isReseting = true
                        let _ = print("Value after button tap: \(isReseting)")// output: Value after button tap: Optional(true)
                    }
                    .padding()
                    .background(item.active ? Color.blue: Color.gray)
                    .foregroundColor(.white)
                    .cornerRadius(30)
                    .frame(width: 100, height: 65)
                }
            }
        }
        .sheet(item: $selectedItem){ item in
            // I'm expecting isReseting to be true here, but it's nil
            SheetView(isReseting: $isReseting)
        }
    }
}

struct SheetView : View {
    @Binding var isReseting : Bool?
    
    var body: some View {
        let _ = print("Value in sheet: \(isReseting)")// outputs: Value in sheet: nil
        
        if isReseting == true {
            Text("It's reseting!!!")
        }else{
            Text("It's NOT reseting")
        }
    }
}

  • Related