EditView Reference is Broken

I've made a simplified version of what I'm trying to do in the included code, but basically I'm trying to make a view that edits a data model. This edit view shows up when a button in a context menu is clicked. The problem is no matter which one I open up the context menu on it always opens up whichever one I clicked first. The reference to the object in the .sheet never changes. How can I fix this??

Example Code:

import SwiftUI

class Object: Identifiable{
    var id: UUID?
    var title: String
    var string: String
    
    init(title: String, string: String) {
        self.id = UUID()
        self.title = title
        self.string = string
    }
}

struct ContentView: View {
    @State private var showingEditView = false
    @State private var objectList = [Object(title: "First", string: "Editor"), Object(title: "Second", string: "Addition"), Object(title: "Third", string: "Twelve")]
    var body: some View {
        NavigationView{
            List{
                Section("Objects"){
                    ForEach(objectList) { object in
                        NavigationLink(destination: ObjectView(obj: object)){
                            Text(object.title)
                        }
                        .sheet(isPresented: $showingEditView){
                            EditView(obj: object)
                        }
                    }
                }
                .contextMenu{
                    Button(action: {
                        self.showingEditView.toggle()
                    }){
                        Text("Edit Item")
                    }
                }
            }
            .listStyle(InsetGroupedListStyle())
            .cornerRadius(10)
            .navigationBarTitle("My List")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct ObjectView: View {
    @State var obj: Object
    var body: some View {
        NavigationView{
            VStack{
                Text(obj.string)
            }
            .navigationBarTitle(obj.title)
        }
    }
}

struct EditView: View {
    @Environment(\.dismiss) var dismiss
    @State var obj: Object
    @State var word = ""
    var body: some View {
        NavigationView{
            Form{
                TextField("Change Word", text: $word)
                    .onAppear(perform: {
                        word = obj.string
                    })
            }
            .navigationBarItems(trailing:
            Button(action: {
                    dismiss()
            }){
                Text("Done")
                    .bold()
            })
        }
    }
}

Any help would be greatly appreciated. All the best!

Answered by BabyJ in 718084022

The problem is that you have attached the .sheet modifier to the view inside of the ForEach, which means that every view has that sheet that presents its own version of EditView. Also the .contextMenu modifier should be attached to each view in the ForEach so it has access to the object that has been selected.

I have changed your code so that this should (theoretically) work.

// When the button in the context menu is tapped, the index of the corresponding object is stored in the selectedObjectIndex variable.
// This triggers the sheet to appear with an unwrapped selectedObjectIndex which is used to get the object that is passed to the EditView.
struct ContentView: View {
    @State private var objectList = [Object(title: "First", string: "Editor"), Object(title: "Second", string: "Addition"), Object(title: "Third", string: "Twelve")]
    @State private var selectedObjectIndex: Int?
    
    var body: some View {
        NavigationView{
            List{
                Section("Objects"){
                    ForEach(objectList) { object in
                        NavigationLink(destination: ObjectView(obj: object)){
                            Text(object.title)
                        }
                        .contextMenu{ // move inside ForEach
                            Button(action: {
                                self.selectedObjectIndex = objectList.firstIndex(of: object)
                            }){
                                Text("Edit Item")
                            }
                        }
                    }
                }
                .sheet(item: $selectedObjectIndex) { index in // move outside ForEach and use item initialiser
                    EditView(obj: objectList[index])
                }
            }
            .listStyle(InsetGroupedListStyle())
            .cornerRadius(10)
            .navigationBarTitle("My List")
        }
    }
}
Accepted Answer

The problem is that you have attached the .sheet modifier to the view inside of the ForEach, which means that every view has that sheet that presents its own version of EditView. Also the .contextMenu modifier should be attached to each view in the ForEach so it has access to the object that has been selected.

I have changed your code so that this should (theoretically) work.

// When the button in the context menu is tapped, the index of the corresponding object is stored in the selectedObjectIndex variable.
// This triggers the sheet to appear with an unwrapped selectedObjectIndex which is used to get the object that is passed to the EditView.
struct ContentView: View {
    @State private var objectList = [Object(title: "First", string: "Editor"), Object(title: "Second", string: "Addition"), Object(title: "Third", string: "Twelve")]
    @State private var selectedObjectIndex: Int?
    
    var body: some View {
        NavigationView{
            List{
                Section("Objects"){
                    ForEach(objectList) { object in
                        NavigationLink(destination: ObjectView(obj: object)){
                            Text(object.title)
                        }
                        .contextMenu{ // move inside ForEach
                            Button(action: {
                                self.selectedObjectIndex = objectList.firstIndex(of: object)
                            }){
                                Text("Edit Item")
                            }
                        }
                    }
                }
                .sheet(item: $selectedObjectIndex) { index in // move outside ForEach and use item initialiser
                    EditView(obj: objectList[index])
                }
            }
            .listStyle(InsetGroupedListStyle())
            .cornerRadius(10)
            .navigationBarTitle("My List")
        }
    }
}

Change to:

    self.selectedObjectIndex = objectList.firstIndex(of: object) ?? 0
EditView Reference is Broken
 
 
Q