Why the first item I tap on a SwiftUI List becomes nil when present it in a sheet

Can someone please explain why the first time I tap on an item from the list, the selectedItem?.name becomes nil? The second time it shows the right item name correctly but not the first time, why?. I was expecting that to have a value even the first time since I'm setting it in the onTapGesture method selectedItem = item.

// model
struct Item: Identifiable{
  var id = UUID()
  var name:String
}

// SwiftUI
struct UpdateStateProperty: View {

  var items:[Item] = [Item(name: "Oranges"),
            Item(name: "Apples"),
            Item(name: "Cookies") ]

  @State private var presentView = false
  @State private var selectedItem: Item?


  var body: some View {
    List{
      ForEach(items){ item in
        HStack{
          Text(item.name)

        }.onTapGesture {
          selectedItem = item
          presentView.toggle()
        }
      }
    }
    .sheet(isPresented: $presentView){
      Text("\(selectedItem?.name)" as String)
    }
  }
}

Accepted Reply

Unfortunately, there's a known issue when you want to use @State variables inside a closure passed to .sheet.

(Seems Apple does not think this issue as a thing to be fixed.)

One possible workaround is declaring another view and pass the binding to it:

struct UpdateStateProperty: View {

  var items:[Item] = [Item(name: "Oranges"),
            Item(name: "Apples"),
            Item(name: "Cookies") ]

  @State private var presentView = false
  @State private var selectedItem: Item?

  var body: some View {
    List{
      ForEach(items){ item in
        HStack{
          Text(item.name)

        }.onTapGesture {
          selectedItem = item
          presentView.toggle()
        }
      }
    }
    .sheet(isPresented: $presentView){
        MySheetView(selectedItem: $selectedItem)
    }
  }
}

struct MySheetView: View {
    @Binding var selectedItem: Item?
    
    var body: some View {
        Text("\(selectedItem?.name ?? "*nil*")")
    }
}
  • Hmm, for some reason I think this used to work in previous versions of Xcode, no? Thank you!.

Add a Comment

Replies

Unfortunately, there's a known issue when you want to use @State variables inside a closure passed to .sheet.

(Seems Apple does not think this issue as a thing to be fixed.)

One possible workaround is declaring another view and pass the binding to it:

struct UpdateStateProperty: View {

  var items:[Item] = [Item(name: "Oranges"),
            Item(name: "Apples"),
            Item(name: "Cookies") ]

  @State private var presentView = false
  @State private var selectedItem: Item?

  var body: some View {
    List{
      ForEach(items){ item in
        HStack{
          Text(item.name)

        }.onTapGesture {
          selectedItem = item
          presentView.toggle()
        }
      }
    }
    .sheet(isPresented: $presentView){
        MySheetView(selectedItem: $selectedItem)
    }
  }
}

struct MySheetView: View {
    @Binding var selectedItem: Item?
    
    var body: some View {
        Text("\(selectedItem?.name ?? "*nil*")")
    }
}
  • Hmm, for some reason I think this used to work in previous versions of Xcode, no? Thank you!.

Add a Comment

You need to use item instead of isPresented, which means you trigger the sheet by assigning/updating the selected value, instead of toggling a boolean value. In code that would be something like this.

struct UpdateStateProperty: View {

  var items:[Item] = [Item(name: "Oranges"),
            Item(name: "Apples"),
            Item(name: "Cookies") ]

  @State private var presentView = false
  @State private var selectedItem: Item?


  var body: some View {
    List{
      ForEach(items){ item in
        HStack{
          Text(item.name)

        }.onTapGesture {
          selectedItem = item
        }
      }
    }
    .sheet(item: $selectedItem) { item in
      Text("\(item?.name ?? "")")
    }
  }
}

I haven't tested the above code, just copied yours, but you got the idea.