Hi everyone!
I'm currently struggling with dynamically filtering data in a SwiftUi List view. In the following example, I created some example data and stored them within "TestArray". These data is dynamically filtered and grouped by the starting letter.
The data of the "Testclass" objects can be changed in "EditDetails" view. Unfortunately, when changing the name (as it is the relevant property for filtering here), when closing the modal, the user does not return to DetailView but will break the view hierarchy and end up in the ContentView. I assume the issue is the update within ContentView, which is recreating the DetailView stack.
Is it possible to ensure the return to view, where the modal has been opened from (DetailView)?
Here is some code if you would like to reproduce the issue:
import Foundation
import SwiftUI
import Combine
struct ContentView: View {
@StateObject var objects = TestArray()
var body: some View {
NavigationView{
List {
ForEach(objects.objectList.compactMap({$0.name.first}).unique(), id: \.self) { obj in
Section(header: Text(String(obj))) {
ForEach(objects.objectList.filter({$0.name.first == obj}), id: \.self) { groupObj in
NavigationLink(destination: Detailview(testclass: groupObj)) {
Text("\(groupObj.name)")
}
}
}
}
}
}
}
}
struct Detailview: View {
@ObservedObject var testclass: Testclass
@State private var showingEdit: Bool = false
var body: some View {
VStack {
Text("Hello, \(testclass.name)!")
Text("\(testclass.date)!")
}
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button(action: {
self.showingEdit.toggle()
}) {
Image(systemName: "square.and.pencil")
}
}
}
.sheet(isPresented: $showingEdit) {
EditDetails(testclass: testclass, showEdit: $showingEdit)
}
}
}
struct EditDetails: View {
@ObservedObject var testclass: Testclass
@Binding var showEdit: Bool
@State private var name: String
@State private var date: Date = Date()
init(testclass: Testclass, showEdit: Binding<Bool>) {
self.testclass = testclass
_name = State(initialValue: testclass.name)
_date = State(initialValue: testclass.date)
_showEdit = Binding(projectedValue: showEdit)
}
var body: some View {
NavigationView{
List {
TextField("Name", text: $name)
.onChange(of: name, perform: { newValue in
self.testclass.name = newValue
})
DatePicker(selection: $date) {
Text("Date")
}
.onChange(of: date, perform: { newValue in
self.testclass.date = newValue
})
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
testclass.objectWillChange.send()
showEdit.toggle()
}) {
Image(systemName: "xmark")
}
}
}
}
}
}
extension Sequence where Iterator.Element: Hashable {
func unique() -> [Iterator.Element] {
var seen: Set<Iterator.Element> = []
return filter { seen.insert($0).inserted }
}
}
class Testclass: NSObject, ObservableObject {
@Published var name: String
@Published var date: Date
init(name: String, date: Date = Date()) {
self.name = name
self.date = date
super.init()
}
}
class TestArray: NSObject, ObservableObject {
@Published var objectList: [Testclass]
private var cancellables = Set<AnyCancellable>()
override init() {
self.objectList = [
Testclass(name: "A1"),
Testclass(name: "B1"),
Testclass(name: "Z2"),
Testclass(name: "C1"),
Testclass(name: "D1"),
Testclass(name: "D2"),
Testclass(name: "A2"),
Testclass(name: "Z1")
]
super.init()
objectList.forEach { object in
object.objectWillChange
.receive(on: DispatchQueue.main) // Optional
.sink(receiveValue: { [weak self] _ in
self?.objectWillChange.send()
print("changed value \(object.name)")
})
.store(in: &cancellables)
}
}
}