Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modifying a @FetchRequest

Tags:

swiftui

Xcode beta 5 introduced @FetchRequest for SwiftUI.

I have a View, which has a @FetchRequest. The NSFetchRequest is created within a manager that makes a static instance of itself available to property wrappers (they cannot use self). The same instance is passed to the view at creation.

What I want is for the FetchRequest to be updated, when self.manager.reverse.toggle() is called, in order for the view to change its ordering of objects. Unfortunately it seems like Manager.fetchRequest() is only called once and then never again, even when I create new objects and transition between different views.

I am looking for suggestions on how to modify a fetch request that is made with the new property wrapper, so that I can resort my objects based on user actions.

struct MainView: some View {
    @ObservedObject var manager: Manager

    @FetchRequest(fetchRequest: Manager.shared.fetchRequest())
    var ts: FetchedResults

    var body: some View {
        NavigationView {
            List(ts, id: \.self) { t in
                Text(t.name)
            }
        }.navigationBarItems(trailing:
            Button(action: { 
                self.manager.reverse.toggle() 
            }) { Text("Reverse")}
    }
}

final class Manager: ObservableObject {
    @Published var reverse: Bool = false

    // Since property wrappers cannot use self, we make a shared instance
    // available statically. This same instance is also given to the view. 
    public static let shared = Manager()

    func fetchRequest(reverse: Bool = false) -> NSFetchRequest<T> {
        let request: NSFetchRequest<T> = T.fetchRequest()

        request.sortDescriptors = [
            NSSortDescriptor(
                key: "name",
                ascending: !self.reverse
            )
        ]

        return request
    }
}
like image 914
Hugo Lundin Avatar asked Aug 09 '19 08:08

Hugo Lundin


1 Answers

This is a very good question! I have been trying to do this and other very similar things with little success. I'm really hoping that this issue will get a suitable resolution in an upcoming beta. However, in the meantime, it is possible to switch between two @FetchRequests to accomplish what you wish to do.

Below is working code from a sample App I'm working on. I use a Button to toggle a Boolean @State, then use that state to pick the appropriate @FetchResults. I don't really like it, but it actually works quite well.

import SwiftUI
import CoreData

struct EntityListView : View {

    @Environment(\.editMode) var editMode

    @State var sortAscending: Bool = true

    @Environment(\.managedObjectContext) var context: NSManagedObjectContext

    @FetchRequest(entity: Entity.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Entity.order, ascending: true)])
    var ascendingEntities: FetchedResults<Entity>

    @FetchRequest(entity: Entity.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Entity.order, ascending: false)])
    var descendingEntities: FetchedResults<Entity>

    var body: some View {

        NavigationView {
            List() {
                Section(header: Text("All Entities".uppercased()))
                {
                    ForEach(sortAscending ? ascendingEntities : descendingEntities) { entity in

                            NavigationLink(destination: EntityEditView(entity: entity)) {

                                HStack {
                                    Text(entity.name)
                                        .font(.headline)
                                    Spacer()
                                    Text(String(entity.order))
                                        .font(.subheadline)
                                        .foregroundColor(.gray)
                                }
                            }
                            }
                            .onMove(perform: self.move)
                            .onDelete(perform: self.delete)
                }

                HStack {
                    Spacer()
                    Button(action: { self.sortAscending.toggle() }) { Text("Reverse Sort") }
                    Spacer()
                }
            }
            .listStyle(GroupedListStyle())
            .navigationBarTitle(Text("Entities"), displayMode: .large)
            .navigationBarItems(trailing: EditButton() )
        }
    }
}
like image 95
Chuck H Avatar answered Oct 12 '22 18:10

Chuck H