100 Days of SwiftUI Day 58

100 Days of SwiftUI – Day 58

We’ve arrived at day 58 of the 100 Days of SwiftUI! We’re continuing our deep dive into Core Data for the next few days. Yesterday, we learned how \.self works, how to save a managed object context only when needed. Today, we’ll learn about NSPredicate, changing fetch requests dynamically, creating relationships, and more. Let’s dive in!

Filtering @FetchRequest using NSPredicate

When we use the @FetchRequest property wrapper, we can provide an array of SortDescriptors to tell SwiftUI how to order the results. We can also provide an NSPredicate object, which is a test to apply to those results. Only objects that pass that test will be in the final array.

//
//  ContentView.swift
//  CoreDataProject

import CoreData
import SwiftUI


struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    
    @FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "universe == %@", "Star Wars")) var ships: FetchedResults<Ship> // Filters ships where the universe is Star Wars.
    var body: some View {
        VStack {
            List(ships, id: \.self) { ship in
                Text(ship.name ?? "Unknown name")
            }
            
            Button("Add Examples") {
                let ship1 = Ship(context: moc)
                ship1.name = "Enterprise"
                ship1.universe = "Star Trek"
                
                let ship2 = Ship(context: moc)
                ship2.name = "Defiant"
                ship2.universe = "Star Trek"
                
                let ship3 = Ship(context: moc)
                ship3.name = "Millenium Falcon"
                ship3.universe = "Star Wars"
                
                let ship4 = Ship(context: moc)
                ship4.name = "Death Star"
                ship4.universe = "Star Wars"
                
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

There are a lot of ways to use NSPredicate, so I would encourage you to check out Paul’s article on them.

Dynamically filtering @FetchRequest with SwiftUI

One of the SwiftUI questions I’ve been asked more than any other is this: how can I dynamically change a Core Data @FetchRequest to use a different predicate or sort order? The question arises because fetch requests are created as a property, so if you try to make them reference another property Swift will refuse.

There is a simple solution here, and it is usually pretty obvious in retrospect because it’s exactly how everything else works: we should carve off the functionality we want into a separate view, then inject values into it.

Hacking with Swift, Paul Hudson (@twostraws)
//
//  ContentView.swift
//  CoreDataProject

import CoreData
import SwiftUI


struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    
    @State private var lastNameFilter = "A"
    var body: some View {
        VStack {
            FilteredList(filterKey: "lastName", filterValue: lastNameFilter) { (singer: Singer) in
                Text("\(singer.wrappedFirstName) \(singer.wrappedLastName)")
            }
            
            
            Button("Add Examples") {
                let taylor = Singer(context: moc)
                taylor.firstName = "Taylor"
                taylor.lastName = "Swift"
                
                let ed = Singer(context: moc)
                ed.firstName = "Ed"
                ed.lastName = "Sheeran"
                
                let adele = Singer(context: moc)
                adele.firstName = "Adele"
                adele.lastName = "Adkins"
                
                try? moc.save()
                
            }
            
            Button("Show A") {
                lastNameFilter = "A"
            }
            
            Button("Show S") {
                lastNameFilter = "S"
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
//
//  DataController.swift


import CoreData // Imports CoreData functions
import Foundation

class DataController: ObservableObject {
    let container = NSPersistentContainer(name: "CoreDataProject") // Tell CoreData to use our CoreDataProject datamodel. NSPersistentContainer is the actual data being saved to the device
    
    init() {
        container.loadPersistentStores { description, error in
            if let error = error {
                print("Core Data failed to load: \(error.localizedDescription)")
                return
            }
            
            self.container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump // merges duplicate objects based on their properties
        }
    }
}
//
//  FilteredList.swift
//  CoreDataProject


import CoreData
import SwiftUI

struct FilteredList<T: NSManagedObject, Content: View>: View {
    @FetchRequest var fetchRequest: FetchedResults<T>
    let content: (T) -> Content
    
    var body: some View {
        List(fetchRequest, id: \.self) { item in
            self.content(item)
        }
    }
    
    init(filterKey: String, filterValue: String, @ViewBuilder content: @escaping (T) -> Content) {
        _fetchRequest = FetchRequest<T>(sortDescriptors: [], predicate: NSPredicate(format: "%K BEGINSWITH %@", filterKey, filterValue))
        self.content = content
    }
}

Because this was quite complex and at times a bit hard to follow, I’ve not made any comments in the code. They would’ve just been way too long. Instead, I would implore you to watch Paul’s video and read the article to ensure you have a decent grasp on what’s going on. Click here to go to it.

One-to-many relationships with Core Data, SwiftUI, and @FetchRequest

Core Data allows us to link entities together using relationships. When we’ll request an object, we’ll get those relationships provided to us automatically. This isn’t quite easy, because this is where Core Data shows it’s age, according to Paul.

Again, I would urge you to go through the video yourself to get a good grasp of what’s going on.

//
//  ContentView.swift
//  CoreDataProject

import CoreData
import SwiftUI


struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    
    @FetchRequest(sortDescriptors: []) var countries: FetchedResults<Country>
    
    var body: some View {
        VStack {
            List(countries, id: \.self) { country in
                Section(country.wrappedFullName) {
                    ForEach(country.candyArray, id: \.self) { candy in
                        Text(candy.wrappedName)
                    }
                }
            }
        }
        
        Button("Add Examples") {
            let candy1 = Candy(context: moc)
            candy1.name = "Mars"
            candy1.origin = Country (context: moc)
            candy1.origin?.shortName = "UK"
            candy1.origin?.fullName = "United Kingdom"
            
            let candy2 = Candy(context: moc)
            candy2.name = "KitKat"
            candy2.origin = Country (context: moc)
            candy2.origin?.shortName = "UK"
            candy2.origin?.fullName = "United Kingdom"
            
            let candy3 = Candy(context: moc)
            candy3.name = "Twix"
            candy3.origin = Country (context: moc)
            candy3.origin?.shortName = "UK"
            candy3.origin?.fullName = "United Kingdom"
            
            let candy4 = Candy(context: moc)
            candy4.name = "Toblerone"
            candy4.origin = Country (context: moc)
            candy4.origin?.shortName = "CH"
            candy4.origin?.fullName = "Switzerland"
            
            try? moc.save()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Wrap up

And that’s it for day 58! It was quite a long day, but for the time we invested, we learned valuable features of Core Data that will help us create more robust and advanced apps in the future. We’re not quite done with Core Data yet though, so it’s time to recharge and look forward to tomorrow. See you then!

Darryl

Hi! My name is Darryl and this is my personal blog where I write about my journey as I learn programming! You'll also find articles about other things that interest me including games, tech and anime.

Post navigation

Leave a Comment

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

100 Days of SwiftUI – Day 33

100 Days of SwiftUI – Day 94

100 Days of SwiftUI – Day 37

100 Days of SwiftUI – Day 10 – Structs