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!
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")
}
}
}