Searchable Views with Core Data and SwiftUI-iOS 15 and iOS 17 improvements
iOS 15 and iOS 17 updates
Dynamic changes in SwiftUI queries with Core Data
As you very well know, continuous improvement is a very important software concept and with the improvement in the operating systems continuously introduced, developers have to make the best of these improvements. iOS 15 came with a new version of SwiftUI which greatly improved things. The change that is relevant for this article is the simplification and expansion of the Core Data API for SwiftUI, which introduced dynamic changing of predicates and sorting criteria for Core Data queries on the spot.
I had covered an approach to enable the use of .searchable with SwiftUI in a previous article published on September 8 (link below)
Using Searchable Views with Core Data in SwiftUI
I was planning to add some search fields to my currently developed application and looking for a clever way to do it with the Core Data implementation I already had. In June 2021 Apple held their Annual World-Wide Developers Conference and introduced many changes to XCode, SwiftUI, UIKit and so on.
Here is the revised, much simpler FetchRequest that is used to fetch all Entry objects:
As you can see, we do not have to explicitly define a Fetch statement or provide a predicate in this simplified syntax. (We completely remove the Fetch function). We provide a sort descriptor, stating which data field we use for sorting and whether the sorting is forward (ascending) or backward (descending).
Now we make a small change in the definition of the filter string.
You may notice that we changed the variable from a @Binding to a @State. We will be able to use this directly and thus no need to introduce this variable in the calling view (ContentView). Now, how do we make sure SwiftUI re-renders the view when the filter string changes? We do it by defining a Binding variable on this and dynamically changing FetchRequest parameters when that variable changes. Check the code below:
When filterString changes, this Binding is activated and it dynamically modifies the nsPredicate property of the FetchedResults variable entries. This triggers a fetch in Core Data and an updated list of objects are read and displayed. Obviously this is much simpler than the previous version. Similar changes can be made in sort descriptors as well (e.g. if there are sorting options on screen, changing these would trigger dynamic changes in the sort descriptor, which would trigger a re-rendering (but no new fetch). Also note that we define an extension to NSPredicate in order to be able to define our own predicate function that will provide the dynamic update of the query.
There is one small trick here. Since we can detect changes in wordFilter only through the Binding, we have to use the Binding on the .searchable modifier and not the original filter string (notice the lack of $, since this is already a binding variable).
As of iOS 17, previews are simplified through the use of a #Preview macro and as such require much less code. See below the old and new ways of defining previews of particular views.