if-statementcore-dataswiftuiswiftui-listnscompoundpredicate

Translate a nested Conditional If Statement into a complex NSCompoundPredicate


I'm writing a macOS target for an old iOS based project using SwiftUI. Is's a Core Data driven application and for the macOS target, I've successfully implemented generic List using a dynamic @FetchRequest, mostly as described by Paul Hudson in his blog.

I've primarily built the target by following Apple's SwiftUI Tutorials and copying the sample code provided.

The previously used conditional if statement actively filtered each SwiftUI List based on 3 UI controls.

Image of three UI controls to "filter" the SwiftUI List

// PART 1
    if (!self.appData.showFavouritesOnly
        || fetchedEvent.isFavourite)
// PART 2
    && (self.searchText.count == 0
        || (fetchedEvent.eventName?.contains(self.searchText) == true))
// PART 3
    && (self.filter == .all
        || self.filter.name == fetchedEvent.eventCategory
        || (self.filter.category == .featured && fetchedEvent.isFeatured)) {

Now that I have a generic @FetchRequest that uses predicates, I want to translate this conditional if statement into an NSCompoundPredicate.

I'll include the entire initialiser so you can see how the dynamic @FetchRequest is built, but it is the predicate that I require assistance with...

init(sortDescriptors: [NSSortDescriptor],
     searchKey: String,
     searchValue: String?,
     showFavourites: Bool,
     filterKey: String,
     filter: FilterType,
     @ViewBuilder content: @escaping (T) -> Content) {

    let entity = T.entity
    let predicateTrue = NSPredicate(value: true)
// PART 1
    let predicateFavourite = showFavourites == false ? predicateTrue : NSPredicate(format: "isFavourite == TRUE")
// PART 2
    let predicateSearch = searchValue?.count == 0 ? predicateTrue : NSPredicate(format: "%K CONTAINS[cd] %@", searchKey, searchValue!)

    // The initialiser works perfectly down to this point...then...
// PART 3
    let predicateFilterName = filter == .all ? predicateTrue : NSPredicate(format: "%K == %@", filterKey, filter.name as CVarArg)
    let predicateFilterFeature = filter.category == .featured ? NSPredicate(format: "isFeatured == TRUE") : predicateTrue

    let predicateOr = NSCompoundPredicate(orPredicateWithSubpredicates: [predicateFilterName, predicateFilterFeature])
    let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateFavourite, predicateSearch, predicateOr])

    fetchRequest =
        FetchRequest<T>(entity: entity,
                        sortDescriptors: sortDescriptors,
                        predicate: predicate)

    self.content = content
}

The code that is included here under Part 3 works partially. Switching between FilterType.all and FilterType.featured makes the expected changes, however I'm struggling to write the predicate for the "other" cases where another Category is chosen - that is - NOT Featured, but EITHER .lakes, .rivers or .mountains.

For completeness, I've also included the enum Category and struct FilterType...

enum Category: String, CaseIterable, Codable, Hashable {

    case featured = "Featured"
    case lakes = "Lakes"
    case rivers = "Rivers"
    case mountains = "Mountains"
}

struct FilterType: CaseIterable, Hashable, Identifiable {

    var name: String
    var category: Category?

    init(_ category: Category) {
        self.name = category.rawValue
        self.category = category
    }

    init(name: String) {
        self.name = name
        self.category = nil
    }

    static var all = FilterType(name: "All")

    static var allCases: [FilterType] {
        return [.all] + Category.allCases.map(FilterType.init)
    }

    var id: FilterType {
        return self
    }

}

Solution

  • I think the problem is here:

     let predicateFilterFeature = filter.category == .featured ? NSPredicate(format: "isFeatured == TRUE") : predicateTrue
    

    If the filter is .lakes etc, then this subpredicate will be TRUE, which when ORed with predicateFilterName overrides it. Try returning FALSE:

     let predicateFilterFeature = filter.category == .featured ? NSPredicate(format: "isFeatured == TRUE") : predicateFalse