In my app, I have a screen where the user can switch filters (of a map) on and off using the Swift Toggle
, and also, delete them (with Edit Button
). I use a Binding
Boolean variable to connect the toggles so that I can react to each change, here is the code:
//The filter data model
class DSFilter{
var filterId : Int
var filterTitle : String
var isActive : Bool
init(filterId : Int, filterTitle : String, isActive : Bool){
self.filterId = filterId
self.filterTitle = filterTitle
self.isActive = isActive
}
}
//The data model representable
struct filterItem : View {
@Binding var filter : DSFilter
@Binding var isOn : Bool
@State var image = Image("defPerson")
var body : some View {
HStack {
image.resizable().frame(width: 45, height: 45).clipShape(Circle())
VStack(alignment: .leading, spacing: 8) {
Text(filter.filterTitle).font(Font.custom("Quicksand-Bold",size: 15))
Text(filter.filterTitle).font(Font.custom("Quicksand-Medium",size: 13)).foregroundColor(.gray)
}.padding(.leading)
Spacer()
HStack{
Toggle("",isOn: $isOn).padding(.trailing, 5).onReceive([self.isOn].publisher.first()) { (value) in
self.filter.isActive = self.isOn
}.frame(width: 30)
}
}.frame(height: 60)
.onAppear(){
self.isOn = self.filter.isActive
}
}
}
//The view where the user has the control
struct FilterView: View {
@State var otherFilters : [addedFilter] = []
@Binding var isActive : Bool
var body: some View {
VStack{
Capsule().fill(Color.black).frame(width: 50, height: 5).padding()
ZStack{
HStack{
Spacer()
Text("Filters").font(Font.custom("Quicksand-Bold", size: 20))
Spacer()
}.padding()
HStack{
Spacer()
EditButton()
}.padding()
}
List{
ForEach(0..<self.otherFilters.count, id:\.self){ i in
filterItem(filter: self.$otherFilters[i].filter, isOn: self.$otherFilters[i].isOn)
}.onDelete(perform:removeRows)
}
Spacer()
}
}
func removeRows(at offsets: IndexSet) {
print("deleting")
print("deleted filter!")
self.otherFilters.remove(at: Array(offsets)[0]) //I use it like so because you can only delete 1 at a time anyway
}
}
//The class I use to bind the isOn value so I can react to change
struct addedFilter : Identifiable{
var id : Int
var filter : DSFilter
var isOn : Bool
init(filter : DSFilter){
self.id = Int.random(in: 0...100000)
self.filter = filter
self.isOn = filter.isActive
}
}
When I use the delete as it is right now I get the following error (Xcode 12.0
):
Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444
2020-10-06 17:42:12.567235+0300 Hynt[5207:528572] Fatal error: Index out of range: file Swift/ContiguousArrayBuffer.swift, line 444
What do I need to change so I can delete the items? Should I take a different approach to the Binding
problem with the Toggle
?
So, after a lot of try and fail I came to the answer. The problem seems to be in the binding part - the compiler can't handle a change in the array size if I bind an element from it so I did the following: First, I change the
filterItem struct
so the filter property is@State
and not@Binding
:Secondly, I have changed the
addedFilter struct
to the following:And the
ForEach
loop to the following:To the main view I have added :
And finally the onDelete function to the following:
In conclution I am not changing the value of the binded value and so I am not receiving any errors!