Translate a nested Conditional If Statement into a complex NSCompoundPredicate

74 Views Asked by At

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
    }

}
2

There are 2 best solutions below

1
On BEST ANSWER

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
0
On

Mine is a fraudulent answer because it is a hacked solution I stumbled across after trying to think about the construction of the predicates in a different manner. (It doesn't demonstrate any careful thought of the actual problem of understanding the syntax of the predicates that I had written!)

So this response doesn't answer my original question - that has been answered by @pbasdf - although this response achieves the same outcome.

So perhaps an alternate solution?!?

let predicateTrue = NSPredicate(value: true)

let predicateFavourite = showFavourites == false ? predicateTrue : NSPredicate(format: "isFavourite == TRUE")

let predicateSearch = searchValue?.count == 0 ? predicateTrue : NSPredicate(format: "%K CONTAINS[cd] %@", searchKey, searchValue!)

let predicateFilterFeatured = NSPredicate(format: "isFeatured == TRUE")
let predicateFilterName = NSPredicate(format: "%K == %@", filterKey, filter.name as CVarArg)
let predicateFilterCategory = filter.category == .featured ? predicateFilterFeatured : predicateFilterName

let predicateFilter = filter == .all ? predicateTrue : predicateFilterCategory

let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateFavourite, predicateSearch, predicateFilter])