Deleting a View Instance with the Tap of a Button

I have a simple project where I have a UUID string followed by a tap button as shown below. If one taps the Add me, the app will list a new instance of a View (KeywordRow).

The following is what I have.

import SwiftUI

struct ContentView: View {
	@ObservedObject var monster: Monster
	
	var body: some View {
		VStack {
			Button {
				monster.items.append(Keyword())
			} label: {
				Text("Add me!")
			}.padding(.vertical, 10.0)
			
			ForEach($monster.items) { item in
				KeywordRow(id: item.id)
			}
		}
	}
}

// MARK: - ObservableObject
class Monster: ObservableObject {
	@Published var items = [Keyword]()
}

// MARK: - Keyword
struct Keyword: Identifiable {
	var id = UUID()
}

struct KeywordRow: View {
	@Binding var id: UUID
	
	var body: some View {
		VStack {
			HStack {
				Text("ID: \(id)")
				Button {
					/* ------ Delete ------ */
				} label: {
					Text("Delete")
				}
			}
		}
	}
}

My question is how I can let the app delete the corresponding instance when I tap the Delete button? I have an ObservedObject variable, which I haven't used. Thanks.

Answered by Claude31 in 710404022

Use environment object, and that works:

struct ContentView: View {
    @ObservedObject var monster: Monster
    
    var body: some View {
        VStack {
            Button {
                monster.items.append(Keyword())
            } label: {
                Text("Add me!")
            }.padding(.vertical, 10.0)
            
            ForEach($monster.items) { item in
                KeywordRow(id: item.id).environmentObject(monster)     // <<-- NEW and essential
            }
        }
    }
}

// MARK: - ObservableObject
class Monster: ObservableObject {
    @Published var items = [Keyword] ()
}

// MARK: - Keyword
struct Keyword: Identifiable {
    var id = UUID()
}

struct KeywordRow: View {
    @Binding var id: UUID
    @EnvironmentObject var monster: Monster   // <<-- NEW
    
    var body: some View {
        VStack {
            HStack {
                Text("ID: \(id)")
                Button {
                    monster.items.removeAll(where: { $0.id == id} )       /* ------ Delete ------ */ // <<-- NEW
                } label: {
                    Text("Delete")
                }
            }
        }
    }
}

And in SceneDelegate,

let contentView = ContentView(monster: Monster()) // <<-- Pass parameter
Accepted Answer

Use environment object, and that works:

struct ContentView: View {
    @ObservedObject var monster: Monster
    
    var body: some View {
        VStack {
            Button {
                monster.items.append(Keyword())
            } label: {
                Text("Add me!")
            }.padding(.vertical, 10.0)
            
            ForEach($monster.items) { item in
                KeywordRow(id: item.id).environmentObject(monster)     // <<-- NEW and essential
            }
        }
    }
}

// MARK: - ObservableObject
class Monster: ObservableObject {
    @Published var items = [Keyword] ()
}

// MARK: - Keyword
struct Keyword: Identifiable {
    var id = UUID()
}

struct KeywordRow: View {
    @Binding var id: UUID
    @EnvironmentObject var monster: Monster   // <<-- NEW
    
    var body: some View {
        VStack {
            HStack {
                Text("ID: \(id)")
                Button {
                    monster.items.removeAll(where: { $0.id == id} )       /* ------ Delete ------ */ // <<-- NEW
                } label: {
                    Text("Delete")
                }
            }
        }
    }
}

And in SceneDelegate,

let contentView = ContentView(monster: Monster()) // <<-- Pass parameter

I got it thanks to Claude31.

import SwiftUI

struct ContentView: View {
	@ObservedObject var monster: Monster
	
	var body: some View {
		VStack {
			Button {
				monster.items.append(Keyword())
			} label: {
				Text("Add me!")
			}.padding(.vertical, 10.0)
			
			ForEach(monster.items) { item in
				KeywordRow(id: item.id).environmentObject(monster)
			}
		}
	}
}

struct Keyword: Identifiable {
	let id = UUID()
}

struct KeywordRow: View {
	@State var id = UUID()
	@EnvironmentObject var monster: Monster
	
	var body: some View {
		VStack {
			HStack {
				Text("ID: \(id)")
				Button {
					monster.items.removeAll(where: { $0.id == id} )
				} label: {
					Text("Delete")
				}
			}
		}
	}
}

Just to give a more comprehensive answer, you could also do it with Binding, instead of environmentObject.

struct ContentView: View {
    @ObservedObject var monster: Monster
    
    var body: some View {
        VStack {
            Button {
                monster.items.append(Keyword())
            } label: {
                Text("Add me!")
            }.padding(.vertical, 10.0)
            
            ForEach($monster.items) { item in
                KeywordRow(id: item.id, items: $monster.items)  // <<-- Changed
            }
        }
    }
}

// MARK: - ObservableObject
class Monster: ObservableObject {
    @Published var items = [Keyword] ()
}

// MARK: - Keyword
struct Keyword: Identifiable {
    var id = UUID()
}

struct KeywordRow: View {
    var id: UUID
    @Binding var items: [Keyword]  // <<-- Here
    
    var body: some View {
        VStack {
            HStack {
                Text("ID: \(id)")
                Button {
                    items.removeAll(where: { $0.id == id} )  // <<-- Changed       /* ------ Delete ------ */
                } label: {
                    Text("Delete")
                }
            }
        }
    }
}

If Monster was only items, then you can even further simplify a little:

struct ContentView: View {
    /* @ObservedObject var monster: Monster */ // No more 
    @State var items = [Keyword]()
    
    var body: some View {
        VStack {
            Button {
                items.append(Keyword())
            } label: {
                Text("Add me!")
            }.padding(.vertical, 10.0)
            
            ForEach(items) { item in
                KeywordRow(id: item.id, items: $items)
            }
        }
    }
}

// MARK: - ObservableObject
//class Monster: ObservableObject {
//    @Published var items = [Keyword] ()
//}

// MARK: - Keyword
struct Keyword: Identifiable {
    var id = UUID()
}

struct KeywordRow: View {
    /* @Binding */ var id: UUID
    @Binding var items: [Keyword]
    
    var body: some View {
        VStack {
            HStack {
                Text("ID: \(id)")
                Button {
                    items.removeAll(where: { $0.id == id} ) /* ------ Delete ------ */
                } label: {
                    Text("Delete")
                }
            }
        }
    }
}

And call as

let contentView = ContentView(items: [Keyword] () )
Deleting a View Instance with the Tap of a Button
 
 
Q