Search code examples
core-dataswiftuistateinitpicker

Can I use @FetchRequest with a @State var in a Picker()?


I cannot figure out how to tie in the @State var to a picker so that @FetchRequest will update.

This code compiles, but changing the picker selection does nothing to fetchRequest, because it's not calling the init. All kinds of other variants have failed mostly.

How do I accomplish this?

    struct ContentView: View {
        @Environment(\.managedObjectContext) private var viewContext
        @State private var selectedSkill: Skill? = nil
        @State private var pickerSelection: Int
        @FetchRequest var skills: FetchedResults<Skill>
        
        init(pickerSelection: Int) {
            self._pickerSelection = State(initialValue: pickerSelection)
            self._skills = FetchRequest(
                entity: Skill.entity(),
                sortDescriptors: [NSSortDescriptor(keyPath: \Skill.value, ascending: true)],
                predicate: NSPredicate(format: "apparatus == %d", pickerSelection)
            )
        }


Solution

  • There are a few ways to go about this here is mine. Not sure of your intended use of Skill but I think you can figure out how to make it work for you.

    I would make apparatus an enum

    enum Apparatus: Int, CaseIterable, CustomStringConvertible, Identifiable {
      case vault, unevenBars, balanceBeam, all
    
      var id: String { self.description }
      var description: String {
        switch self {
            case .vault: return "Vault"
            case .unevenBars: return "Uneven Bars"
            case .balanceBeam: return "Balance Beam"
            case .all: return "All"
        }
      }
    }
    

    extend on Item

    extension Item {
      var unwrappedApparatus: Apparatus {
        Apparatus(rawValue: Int(apparatus)) ?? Apparatus.vault
      }
    
      var unwrappedName: String {
        name ?? "Unknown"
      }
    }
    

    Set your @State in ContentView

    struct ContentView: View {
      @Environment(\.managedObjectContext) private var viewContext
      @State var selectedApparatus = Apparatus.vault
    
      var body: some View {
        List {
            Picker("Apparatus", selection: $selectedApparatus) {
                Text("Vault").tag(Apparatus.vault)
                Text("Uneven Bars").tag(Apparatus.unevenBars)
                Text("Balance Beam").tag(Apparatus.balanceBeam)
                Text("All").tag(Apparatus.all)
            }
    
            Text(selectedApparatus.description)
    
            ItemsView(selectedApparatus: $selectedApparatus) // <-- View changing on selectedApparatus
        }
      }
    }
    

    Now break out the View you want to change based on the selectedApparatus to its own View

    struct ItemsView: View {
      @FetchRequest private var items: FetchedResults<Item>
    
      init(selectedApparatus: Binding<Apparatus>) {
        self._items = FetchRequest(entity: Item.entity(),
                                   sortDescriptors: [NSSortDescriptor(keyPath: \Item.name, ascending: true)],
                                   predicate: selectedApparatus.wrappedValue == .all ? nil : NSPredicate(format: "apparatus == %d", selectedApparatus.wrappedValue.rawValue))
    }
    
      var body: some View {
        ForEach(items) { item in
            Text(item.unwrappedName)
        }
      }
    }