Reputation: 537
I have created a quiet simple list in SwiftUI and want to make it editable, like a tableView in UIKit. I want to remove a row in the list with the all known gesture (swipe from, the right to the left).
I have tried to make with a button above the list, but it doesn't look nice an is not practicable for my app.
struct singleIsland: Identifiable {
let id: Int
let name:String
}
var islands = [
singleIsland(id: 0, name: "Wangerooge"),
singleIsland(id: 1, name: "Spiekeroog"),
singleIsland(id: 2, name: "Langeoog")
]
var body: some View {
VStack {
List(islands) { island in
Text(island.name)
}
}
}
Upvotes: 8
Views: 12391
Reputation: 119272
Add this to the list:
.onDelete { $0.forEach { islands.remove(at: $0) } }
After you turning islands
into an @State
Upvotes: 3
Reputation: 77631
Yes, this is very straight forward with SwiftUI.
Updating your code like this...
struct SingleIsland: Identifiable {
let id: Int
let name:String
}
struct IslandListView: View {
@State private var islands = [
SingleIsland(id: 0, name: "Wangerooge"),
SingleIsland(id: 1, name: "Spiekeroog"),
SingleIsland(id: 2, name: "Langeoog")
]
var body: some View {
List {
ForEach(islands.identified(by: \.name)) { island in
Text(island.name)
}.onDelete(perform: delete)
}
}
func delete(at offsets: IndexSet) {
islands.remove(at: offsets)
}
}
This will allow your view to swipe to delete rows.
Using @State
sets up your view to depend on the islands
array. Any update to that array will trigger the view to reload. So by deleting an item from the array it will animate the change to the list.
Upvotes: 5
Reputation: 871
you can not do that with a static list.
in the real world your list of islands would probably come from the outside of your view anyway.
we use your struct:
[...]
struct singleIsland: Identifiable {
var id: Int
var name:String
}
[...]
and create an bindable Object to hold those islands
[...]
class IslandStore : BindableObject {
let didChange = PassthroughSubject<IslandStore, Never>()
var islands : [singleIsland] {
didSet { didChange.send(self) }
}
init (islands: [singleIsland] = []){
self.islands = islands
}
}
[...]
you need to import combine to use BindableObject
[...]
import SwiftUI
import Combine
[...]
your view now binds the island store the .onDelete(perform: delete) automatically adds the swipe left to delete function. We have to code the delete function tho:
[...]
struct ForTesting : View {
@ObjectBinding var store = IslandStore()
var body: some View {
List {
ForEach(store.islands) { island in
Text(island.name)
}.onDelete(perform: delete)
}
}
func delete(at offsets: IndexSet) {
// theres seems to be a bug that prevents us from using atOffsets
// so we convert to index
guard let index = Array(offsets).first else { return }
store.islands.remove(at: index)
}
}
[...]
and while we are at it we add a EditButton() and a title. We have to wrap our list in a NavigationView to do this
[...]
struct ForTesting : View {
@ObjectBinding var store = IslandStore()
var body: some View {
NavigationView {
List {
ForEach(store.islands) { island in
Text(island.name)
}.onDelete(perform: delete)
}
.navigationBarTitle(Text("Islands"))
.navigationBarItems(trailing: EditButton())
}
}
func delete(at offsets: IndexSet) {
// theres seems to be a bug that prevents us from using atOffsets
// so we convert to index
guard let index = Array(offsets).first else { return }
store.islands.remove(at: index)
}
}
[...]
change your DEBUG section to initialise the islands store and hand it over to your view:
#if DEBUG
var islands = [
singleIsland(id: 0, name: "Wangerooge"),
singleIsland(id: 1, name: "Spiekeroog"),
singleIsland(id: 2, name: "Langeoog"),
singleIsland(id: 3, name: "Baltrum")
]
struct ForTesting_Previews : PreviewProvider {
static var previews: some View {
ForTesting(store: IslandStore(islands:islands))
}
}
#endif
Upvotes: 2
Reputation: 22846
struct SingleIsland {
let name: String
}
struct ContentView: View {
@State var islands = [
SingleIsland(name: "Wangerooge"),
SingleIsland(name: "Spiekeroog"),
SingleIsland(name: "Langeoog")
]
var body: some View {
List {
ForEach(islands.identified(by: \.name)) { island in
Text(island.name)
}.onDelete(perform: delete)
}
}
private func delete(with indexSet: IndexSet) {
indexSet.forEach { islands.remove(at: $0) }
}
}
Wrapping the data in a @State
makes sure the view is redrawn if it changes.
Note:
I'm getting compilers errors if the List
is built this way:
List(data) { item in
[...]
}
It will complain that onDelete
does not exist for the List
.
My workaround is to use a ForEach
inside the List
, and the onDelete
function on it.
Upvotes: 10