xcodeswiftuinspredicatepredicatensfetchrequest

SwiftUI - Dynamic NSPredicate If Statement


How can I create a predicate so that when the user selects "Full Body" it returns the entire list with no predicate? Right now, it is returning "part" which corresponds to the muscle groups I have set (Abs, Legs, Push, Pull). I want to return all of the options when "Full Body" is selected. How could I write an If statement so that the predicate is not used?

import SwiftUI

var parts = ["Abs", "Legs", "Push", "Pull", "Full Body"]
struct ExerciseList: View {
    
    @State private var selectedPart = " "
    
    var body: some View {
        NavigationView {
            VStack (alignment: .leading) {
                
                NavigationLink(destination: AddExerciseView()){
                    Text("Add Exercise")
                        .fontWeight(.bold)
                }
                
                Picker("Body Part", selection: $selectedPart) {
                    ForEach(parts, id:\.self) { part in
                        Text(part)
                    }
                }.pickerStyle(.segmented)
                
                ListView(part:selectedPart)
            }    
        }
    }
}

import SwiftUI

struct ListView: View {
    
    var part: String
    
    @FetchRequest var exercises: FetchedResults<Exercise>
    
    init(part: String) {
        self.part = part
        self._exercises = FetchRequest(
            entity: Exercise.entity(),
            sortDescriptors: [],

            predicate: NSPredicate(format: "musclegroup == %@", part as any CVarArg)
        )
    }
    
    var body: some View {
        List(exercises) { e in
            Text(e.exercisename)
        }
    }
}

Solution

  • It's not a good idea to init objects inside View structs because the heap allocation slows things down. You could either have all the predicates created before hand or create one when the picker value changes, e.g. something like this:

    // all the Picker samples in the docs tend to use enums.
    enum Part: String, Identifiable, CaseIterable {
        case abs
        case legs
        case push
        case pull
        case fullBody
        
        var id: Self { self }
    
    // Apple sometimes does it like this
    //    var localizedName: LocalizedStringKey {
    //        switch self {
    //            case .abs: return "Abs"
    //            case .legs: return "Legs"
    //            case .push: return "Push"
    //            case .pull: return "Pull"
    //            case .fullBody: return "Full Body"
    //        }
    //    }
        
       
    }
    
    struct ExerciseListConfig {
        var selectedPart: Part = .fullBody {
            didSet {
                if selectedPart == .fullBody {
                    predicate = nil
                }
                else {
                    // note this will use the lower case string
                    predicate = NSPredicate(format: "musclegroup == %@", selectedPart.rawValue)
                }
            }
        }
        var predicate: NSPredicate?
    }
    
    struct ExerciseList: View {
        
        @State private var config = ExerciseListConfig()
        
        var body: some View {
            NavigationView {
                VStack (alignment: .leading) {
                    Picker("Body Part", selection: $config.selectedPart) {
                        //ForEach(Part.allCases) // Apple sometimes does this but means you can't easily change the display order.
                        Text("Abs").tag(Part.abs)
                        Text("Legs").tag(Part.legs)
                        Text("Push").tag(Part.push)
                        Text("Pull").tag(Part.pull)
                        Text("Full Body").tag(Part.fullBody)
                        
                    }.pickerStyle(.segmented)
                    
                    ExerciseListView(predicate:config.predicate)
                }
            }
        }
    }
    
    
    struct ExerciseListView: View {
        
       // var part: String
        let predicate: NSPredicate?
      //  @FetchRequest var exercises: FetchedResults<Exercise>
        
        init(predicate: NSPredicate?) {
           self.predicate = predicate
    //        self._exercises = FetchRequest(
    //            entity: Exercise.entity(),
    //            sortDescriptors: [],
    //
    //            predicate: NSPredicate(format: "musclegroup == %@", part as any CVarArg)
    //        )
        }
        
        var body: some View {
            Text(predicate?.description ?? "")
    //        List(exercises) { e in
    //            Text(e.exercisename)
    //        }
        }
    }
    

    Since you are using Core Data you might want to use an Int enum in the entity for less storage and faster queries.