Skip to content
vrhermit

vrhermit

Code & Writing by Joseph Simpson

  • Canvatorium
  • Technical
  • Professional
  • Personal
vrhermit
vrhermit
Code & Writing by Joseph Simpson
Retrospective | Technical

Dynamic Sort Descriptors and Predicates

ByJoseph October 13, 2019June 25, 2023
CoreData • SwiftUI

I based most of my core data code for Retrospective Timelines on an example project that you can check out here. The developer sent me the link to this in a comment on stack overflow a couple of months ago. Throughout the project I’ve made small changes to this to better suite my needs.

Today I made a huge set of changes. I wanted a way to build UI controls that can modify the sort descriptors and predicates that the fetch requests used to drive the FRC. In the sample project this is done by passing in some optional strings, then parsing them into the objects they need to be in the a function that prepares the fetch request. This approach did not scale for what I need, as some of the layouts need complex sorting and/or predicates. I made a new version of this that replaces the optional string properties with some alternatives.

First the sort descriptors. Core Data has a way to apply multiple sort descriptors by passing them as an array. Even if I only have one sort order (rare for this project) I can just pass a single descriptor in the array

private var sortDescriptors: [NSSortDescriptor]?

The predicates were a bit different. Core data accepts one predicate for a fetch request, not multiple… kinda. Predicates can be combined using compounds. I can make compound predicates on each layout as needed.

private var predicate: NSPredicate?

Then I needed a way to add these to the fetched request.

private func configureFetchRequest() -> NSFetchRequest<T> {
        let fetchRequest: NSFetchRequest<T> = T.fetchRequest() as! NSFetchRequest<T>
        fetchRequest.fetchBatchSize = 0
        
        if let sortDescriptors = self.sortDescriptors {
            fetchRequest.sortDescriptors = sortDescriptors
        }
        
        if let predicate = self.predicate {
            fetchRequest.predicate = predicate
        }
        
        return fetchRequest
    }

I need a way to call this publicly as well. This calls the private method after checking the optionals. If I no longer have a descriptor or predicate, I set the property back to nil so it’s no longer used in configureFetchRequest

public func loadDataSource(sort: [NSSortDescriptor]?, predicate: NSPredicate?) -> [T] {
        
        if let sort = sort {
            self.sortDescriptors = sort
        } else {
            self.sortDescriptors = nil
        }
        
        if let predicate = predicate {
            self.predicate = predicate
        } else {
            self.predicate = nil
        }
        
        self.fetchRequest = configureFetchRequest()
        self.frc = configureFetchedResultsController()
        
        return self.allInOrder
    }

Now for the cool part. I can make a View Model for each layout where I can place some properties to drive controls in the user interface. This View Model will also handle building the sort descriptors and predicates for the layout. They can then be used as a parameter when I call loadDataSource.

Here is a basic example of the View Model that drives the list of event dates. The sortToggle variable is bound to a UI toggle so when the user taps it the sort order changes. I’ll replace this with a better sort button, but the underlying concept will remain the same.

class EventListVM: ObservableObject {
    
    @Published var sortToggle = false

    public func getSort() -> [NSSortDescriptor] {
        return [NSSortDescriptor(key: "isOngoing", ascending: sortToggle), NSSortDescriptor(key: "date", ascending: sortToggle)]
    }
    
    public func getPredicate(timeline: Timeline?) -> NSPredicate? {
        if let timeline = timeline {
            
            let startString = String(format: "%@%@", "dateStartEvent.eventTimeline", " == %@")
            let startPredicate = NSPredicate(format: startString, timeline)
            
            let endString = String(format: "%@%@", "dateEndEvent.eventTimeline", " == %@")
            let endPredicate = NSPredicate(format: endString, timeline)
            
            let compoundPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [startPredicate, endPredicate])
            return compoundPredicate
        }
        return nil
    }
    
}

The only thing that is a little nuts is the way I get the records to use in the ForEach view. This calls a method on one ObservedObject while using return values from two functions on another ObservedObject. I feel like I’m getting away with something here.

@ObservedObject var dataSource = RADDataSource<RTDate>()
@ObservedObject var eventListVM = EventListVM()

...

ForEach(dataSource.loadDataSource(sort: self.eventListVM.getSort(), predicate: self.eventListVM.getPredicate(timeline: self.timeline))) { rtDate in
...
}

This is just a simple example, but now that I have the foundation in place I can extend this to work on much more complex user interfaces.

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Tumblr (Opens in new window) Tumblr
  • More
  • Click to print (Opens in new window) Print
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to share on Pinterest (Opens in new window) Pinterest
  • Click to share on Telegram (Opens in new window) Telegram
  • Click to share on WhatsApp (Opens in new window) WhatsApp

Like this:

Like Loading...
Post Tags: #CoreData#SwiftUI
Next: UI as of Oct 14
Previous: Lazy sorting

Get my articles in your email

Subscribe

A-Frame AI AppUpdate AR BabylonJS Books Career ChatGPT CloudKit CoreData FileMaker Food Gaming Kadence MixedReality parody PlayCanvas Podcast SpatialComputing SwiftUI Thoughts visionOS VisionPro VR VueJS WebXR WordPress

Work with Me

Ready to streamline your workflows and enhance your digital presence?

Do you want to take your first step into Spatial Computing?

Discover how Radical Application Development can help transform your business.

Get in touch or learn more at radicalappdev.com

Mastodon Mastodon
Privacy & Cookies: This site uses cookies. By continuing to use this website, you agree to their use.
To find out more, including how to control cookies, see here: Privacy Policy

Follow my work

Github Linkedin YouTube

© 2025 Joseph Simpson | Radical Application Development

  • Canvatorium
  • Technical
  • Professional
  • Personal
Search
%d