2

I'm having an issue presenting a Sheet when selecting one of the options in a ContextMenu. The first time that the Sheet is presented, it's missing the initial data. Following presentations work just fine, it's just the first one that presents the wrong behavior. Has anyone seen anything similar? I'm wondering if it's just a bug or I'm doing something wrong.

Sample code to demonstrate the issue:

struct ContentView: View {
    
    var options =  ["One", "Two", "Three"]
    
    @State var modalText: String?
    @State var isShowingModal = false
    
    var body: some View {
        List(options, id: \.self) { option in
            Text(option).contextMenu {
                Button("Edit") {
                    // Store the option that we want to modify
                    modalText = option
                    // Trigger the modal
                    isShowingModal = true
                }
            }
        }.toolbar {
            Button {
                // Trigger the modal with no previous data because it's creation
                isShowingModal = true
            } label: {
                Text("Create")
            }
        }.sheet(isPresented: $isShowingModal) {
            // Reset the state, the modal might be used to edit or create
            // (create doesn't pass an initial value)
            modalText = nil
        } content: {
            Text("Edit or Create modal for \(modalText ?? "NOTHING")")
        }
    }
}

Video demonstrating the issue: https://twitter.com/xmollv/status/1413028616112414721

Xavi Moll
  • 247
  • 2
  • 14
  • 1
    The culprit is using `sheet(isPresented:)` instead of `sheet(item:)`. `sheet(isPresented:)` always shows the *first* render of the sheet content when first presented, which is always `nil` in your case. This behavior was introduced in iOS 14 is appears often on SO. See: https://stackoverflow.com/a/66190152/560942 https://stackoverflow.com/a/66287123/560942 https://stackoverflow.com/a/66936503/560942 etc – jnpdx Jul 08 '21 at 09:09
  • @jnpdx Indeed using the `sheet(item:)` fixed the issue, thanks for pointing me to the answers! I've marked my question as duplicated, thanks everyone! – Xavi Moll Jul 09 '21 at 08:25
  • Glad I could help. If one of those answers I linked to was helpful, you could upvote it. – jnpdx Jul 09 '21 at 17:35
  • @jnpdx Done, thanks for your help! – Xavi Moll Jul 09 '21 at 19:54

2 Answers2

0

I don't really understand why your code does not work. But you could try this to make it work:

class ModalText: ObservableObject {
    @Published var text: String?
}

struct ContentView: View {
    var options =  ["One", "Two", "Three"]
    
    @StateObject var modalText = ModalText()
    @State var isShowingModal = false
    
    var body: some View {
        List(options, id: \.self) { option in
            Text(option).contextMenu {
                Button("Edit") {
                    modalText.text = option
                    isShowingModal = true
                }
            }
        }
        .toolbar {
            Button {
                // Trigger the modal with no previous data because it's creation
                isShowingModal = true
            } label: {
                Text("Create")
            }
        }
        .sheet(isPresented: $isShowingModal) {
            // Reset the state, the modal might be used to edit or create
            // (create doesn't pass an initial value)
            modalText.text = nil
        } content: {
            Text("Edit or Create modal for \(modalText.text ?? "NOTHING")")
        }
    }
}
0

Use selection parameter and it will work:

List(options,id: \.self, selection: $modalText) { option in
    Text(option).contextMenu {
        Button("Edit") {
            // Store the option that we want to modify
            modalText = option
            // Trigger the modal
            isShowingModal = true
        }
    }
}
RTXGamer
  • 3,215
  • 6
  • 20
  • 29