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