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.
// 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
}
}
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