translation
www.hackingwithswift.com/books/ios-s…


For more content, please follow the public account “Swift Garden”.


Like articles? How about 🔺💛➕ company 3? Follow this column, follow me 🚀🚀🚀

Dynamically filter @fetchRequest in SwiftUI

One question I’m often asked about SwiftUI is: How do I dynamically change a Core Data@FetchRequest to use a different predicate or sort? People raise this issue because fetch requests are created as attributes, so if you try to get them to refer to another attribute, Swift will reject them.

Here’s a simple solution, and if you think about it, it’s pretty obvious: Just about everything else works the same way: We should split this functionality into a separate view and inject values into it.

I wanted to illustrate this with some actual code, so I put the simplest example below: Add three singers to Core Data, and use two buttons to display singers whose last names end in A or S.

Create a Core Data entity called Singer and give it two strings: “firstName” and “lastName”. Use the data Model inspector to change its Codegen to Manual/None, then go to the Editor menu and select Create NSManagedObject Subclass so that we get a Singer class that can be customized.

Once Xcode has generated the file for us, open Singer+CoreDataProperties. Swift and add the following two properties to make this class work better with SwiftUI:

var wrappedFirstName: String {
    firstName ?? "Unknown"
}

var wrappedLastName: String {
    lastName ?? "Unknown"
}Copy the code

Next comes the actual work.

The next step is to design a view that hosts our information. As I mentioned earlier, it will have two buttons, a filter to change the view information, and a button to insert test data.

First, add two properties to the ContentView structure so that we have managed object context that holds the object, and a state to use as a filter:

@Environment(\.managedObjectContext) var moc
@State private var lastNameFilter = "A"Copy the code

For the body of the view, we’ll wrap the three buttons in a VStack with a comment placeholder for the list of matching artists:

VStack {
    // list of matching singers

    Button("Add Examples") {
        let taylor = Singer(context: self.moc)
        taylor.firstName = "Taylor"
        taylor.lastName = "Swift"

        let ed = Singer(context: self.moc)
        ed.firstName = "Ed"
        ed.lastName = "Sheeran"

        let adele = Singer(context: self.moc)
        adele.firstName = "Adele"
        adele.lastName = "Adkins"

        try? self.moc.save()
    }

    Button("Show A") {
        self.lastNameFilter = "A"
    }

    Button("Show S") {
        self.lastNameFilter = "S"}}Copy the code

So far, so simple, now comes the fun part: We need to replace the // List of Matching singers comment with an actual implementation. We won’t use the @fetchRequest annotation here, because we’re creating a custom FETCH request in the constructor, but the code is pretty much the same.

Create a SwiftUI view called “FilteredList” and give it this property:

var fetchRequest: FetchRequest<Singer>Copy the code

This property is used to store our fetch request so that we can iterate through it in the body. However, we don’t create it right away because we don’t know what we’re looking for yet. Instead, we create a custom constructor that takes a filter string and uses that string to set the fetchRequest property.

Add the following constructor:

init(filter: String) {
    fetchRequest = FetchRequest<Singer>(entity: Singer.entity(), sortDescriptors: [], predicate: NSPredicate(format: "lastName BEGINSWITH %@".filter))}Copy the code

This results in a FETCH request built with the current managed object context. Because this view will be used inside the ContentView, we don’t have to inject the managed object context into the environment — it inherits the context from the ContentView.

All that’s left is to complete the body of the view, and the only interesting thing here is that without the @FetchRequest, we need to access the wrappedValue property of the FetchRequest to pull out our data. Therefore, the body implementation of the view looks like this:

var body: some View {
    List(fetchRequest.wrappedValue, id: \.self) { singer in
        Text("\(singer.wrappedFirstName) \(singer.wrappedLastName)")}}Copy the code

If you don’t like to use fetchRequest wrappedValue, can create a simple calculation attributes:

var singers: FetchedResults<Singer> { fetchRequest.wrappedValue }Copy the code

As for the preview of the FilteredList, you can remove it directly.

Now that the view is complete, we can go back to the ContentView and implement the comment in actual code, as follows:

FilteredList(filter: lastNameFilter)Copy the code

Run the app and try: click the Add Examples button to create three singers first, then click “Show A” or “Show S” to trigger A different last name filter. You should see the list dynamically updated with different data as you click on different buttons.

To make it all work, a bit of new knowledge is used, but it’s not hard — as long as you think SwiftUI, the answers will pop out on the page.


My official account here Swift and computer programming related articles, as well as excellent translation of foreign articles, welcome to pay attention to ~