How do I filter a list populated with SwiftData objects from array via custom filter chips

70 Views Asked by At

I have an app where I want to filter an array of objects stored in SwiftData by using filter chips. I have build both components and the chips to update based on a toggle of .onTapGesture inside their FilterChipView. However in my main content view (HomeViewViewController) the list of projects is not updated. It seams like I lose the info somewhere that a filter has been tapped so please update view accordingly.

updated FilterModel:

struct FilterModel: Identifiable {
    
    @State var isFilterTapped: Bool **--> zu var isFilterTapped: Bool**
    let id = UUID()
    let filterIcon: String
    let filterKey: ProjectStatus.Status

}

fixed FilterChipView:

struct FilterChipView: View {
    
    let chipIcon: String
    let titleKey: ProjectStatus.Status.RawValue
    @**Binding** var isFilterTapped: Bool
    
    var body: some View {
     
        HStack(spacing: 4) {
            
            Image.init(systemName: chipIcon)
                .font(.body)
            
            Text(titleKey)
                .font(.body)
                .lineLimit(1)
                        
        }
        .padding(.vertical, 4)
        .padding(.leading, 4)
        .padding(.trailing, 10)
        .foregroundColor(isFilterTapped ? .white : Color(UIColor.darkGray))
        .background(isFilterTapped ? Color(UIColor.timeVizLightBlue) : Color(UIColor.systemGray6))
        .cornerRadius(20)
//        .overlay(
//            RoundedRectangle(cornerRadius: 20)
//                .stroke(Color.blue, lineWidth: 1.5)
//            
//        )
        .onTapGesture {
            isFilterTapped.toggle()
        }
    }
}

FilterChipViewModel:

class FilterViewModel: ObservableObject {
        
    @Published var filters: [FilterModel] = [
        FilterModel(isFilterTapped: false, filterIcon: "circle.fill", filterKey: .open),
        FilterModel(isFilterTapped: false, filterIcon: "checkmark.circle.fill", filterKey: .done),
        FilterModel(isFilterTapped: false, filterIcon: "bolt.circle.fill", filterKey: .released)
    ]

}

updated & fixed FilterChipContainerView:

struct FilterChipContainerView: View {
    
    @ObservedObject var filterViewModel = FilterViewModel() **--> zu @ObservedObject var filterViewModel: FilterViewModel**
        
    var body: some View {
                
        HStack {
            
            ForEach(**$**filterViewModel.filters) { **$**filter in
                
                FilterChipView(
                    chipIcon: filter.filterIcon,
                    titleKey: filter.filterKey.rawValue,
                    isFilterTapped: **$**filter.isFilterTapped
                )
                
            }
        }
    }
    
}

ContentView (HomeViewViewController):

struct HomeViewViewController: View {
    
    @Query(sort: \ProjectModel.projectTitle) var projects: [ProjectModel]
    
    @State var selectedStatus: ProjectStatus.Status = .done
    
    @StateObject var filterViewModel = FilterViewModel()
    
    @Environment(\.modelContext) var modelContext
    
    var body: some View {
                
        NavigationStack {
                
            Text("meine Projekte:")
                .font(.title)
                .foregroundStyle(.black)
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding()
            
            VStack(alignment: .center, spacing: 16) {
                
                FilterChipContainerView(filterViewModel: filterViewModel)
                                
            }
            .padding(.horizontal, 16)
            
            List(filterViewModel.filters) { filter in
                
                if filter.isFilterTapped {
                    
                    Text("Filter gedrücket")
                    
                    ForEach(projects, id: \.id) { project in

                        if filter.isFilterTapped && filter.filterKey == project.projectStatus {
                            ProjectCard(project: project)
                        }
                        
                    }
                    .onDelete(perform: deleteProject)
                    .listRowSeparator(.hidden)
                    
                }

            }
            .listStyle(.plain)
            .listRowBackground(Color.clear)
            .listRowSpacing(10)
            .safeAreaInset(edge: .bottom) {
            
                AddNewProjectView()
                    
            }
        }
    }
    
    private func deleteProject(_ indexSet: IndexSet) {
        for index in indexSet {
            let project = projects[index]
            modelContext.delete(project)
        }
    }
}

extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

#Preview {
    HomeViewViewController()
}

When I manually set the filters in the viewModel to "isFilterTapped: true" or false the filters do work. But they do not update / update the List based on selection in app. Any idea how I can fix this?

Thanks a ton in advance I have been trying to get this to work for the past three days without any luck... :/ You shall be my "Ehre-Person" :)

I have tried using @StateObject and @ObservedObject but neither seam to update the contentView when a filter is tapped. This is driving me nuts.. :/

0

There are 0 best solutions below