srome11
srome11

Reputation: 83

Dynamically filtering Coredata FetchRequest and keeping it updated

I'm having an issue dynamically filtering my fetch request. Right now it's depending on a secondary array 'filteredResults', but because of this, anytime the core data entity is deleted or changed, the annotation still exists on the map.

I did see something on hacking with swift that suggested separating out the MapView and mapContentView, but that didnt work either.

Any help or direction is greatly appreciated!

struct MapView: View {
    
    @Environment(\.dismiss) private var dismiss
    
    @State private var position: MapCameraPosition = .automatic
    @State private var visibleRegion: MKCoordinateRegion?   // region visible on map
    
    @State private var selectedAnnotation: LocationEntity?
    @State private var selectedFilters: [filterType] = []
    
    @FetchRequest(entity: LocationEntity.entity(), sortDescriptors: []) private var allLocations: FetchedResults<LocationEntity>
    
    var filteredResults: [LocationEntity] {
            allLocations.filter { location in
                selectedFilters.isEmpty ||
                selectedFilters.contains(where: { filter in
                    switch filter {
                    case .orgs:
                        return location.type == 1
                    case .people:
                        return location.type == 2
                    case .tasks:
                        return location.type == 3
                    case .all:
                        return true
                    }
                })
            }
        }
    
    var body: some View {
        ZStack {
            Map(position: $position, selection: $selectedAnnotation){
                ForEach( filteredResults, id: \.self) { entity in
                    Annotation("Pin", coordinate: entity.coordinate, anchor: .bottom) {
                        MapAnnotationPin(entity: entity)
                    }
                    .annotationTitles(.hidden)
//                    .tag(entity.id)
                    }
            }
            VStack(spacing: 0) {
                Spacer()
                annotationDetailView()
            }
        }
        .navigationBarBackButtonHidden()
        .safeAreaInset(edge: .bottom, content: {
                HStack {
                    Spacer()
                    MapBottomBar(selectedFilters: $selectedFilters)
                        .padding(.top)
                    Spacer()
                }
                .background(.thinMaterial)
        })
struct MapBottomBar: View {
    @Binding var selectedFilters: [filterType]

    var body: some View {
        HStack {
            // Button for Orgs
            Button(action: {
                toggleFilter(.orgs)
            }, label: {
                Label("Orgs", systemImage: "building.2")
            })
            .buttonStyle(isSelected: selectedFilters.contains(.orgs))

            // Button for People
            Button(action: {
                toggleFilter(.people)
            }, label: {
                Label("People", systemImage: "person.2")
            })
            .buttonStyle(isSelected: selectedFilters.contains(.people))

            // Button for Tasks
            Button(action: {
                toggleFilter(.tasks)
            }, label: {
                Label("Tasks", systemImage: "list.dash.header.rectangle")
            })
            .buttonStyle(isSelected: selectedFilters.contains(.tasks))
        }
    }

    private func toggleFilter(_ filter: filterType) {
        if selectedFilters.contains(filter) {
            selectedFilters.removeAll(where: { $0 == filter })
        } else {
            selectedFilters.append(filter)
        }
    }
}

Upvotes: 1

Views: 188

Answers (1)

malhal
malhal

Reputation: 30549

Don't filter yourself just set the predicate, you could use this generic fetching View, e.g.

// compute a new fetchrequest with predicate
FetchedResultsView(request: FetchRequest(predicate:NSPredicate(format: "type IN %@", selectedFilters))
} results in {
    ForEach(results) { result in
        Annotation("Pin", coordinate: result.coordinate, anchor: .bottom) {
            MapAnnotationPin(title: result.title)
        }
        .annotationTitles(.hidden)
        .tag(result.id)
    }
}


struct FetchedResultsView<Content, Result>: View where Content: View, Result: NSFetchRequestResult {
    @FetchRequest var results: FetchedResults<Result>
    let content: ((FetchedResults<Result>) -> Content)
    
    init(request: FetchRequest<Result>, @ViewBuilder content: @escaping (FetchedResults<Result>) -> Content) {
        self._results = request
        self.content = content
    }
    
    var body: some View {
        content(results)
    }
}

Upvotes: 0

Related Questions