Integrating CoreData with new SwiftUI App protocol

Hi,

Xcode 12 (beta) has some new templated code when creating a new App supporting CoreData.
It would be good if your book finally addressed working SwiftUI now that it is the default for new projects.

Example of topic to cover:

  1. working with a struct (as shown below) or class for the CoreDataStack? (particularly in MVVM setup where a viewModel will be handling CoreData interactions instead of leveraging property wrappers inside Views)
  2. using @StateObject vs @ObservedObject for a viewModel var in a View
  3. using property wrappers directly in a View vs the MVVM handling of data operations in a separate store (aka ViewModel class, or whatever people choose to call it).

Thanks!

1 Like

— PersistenceController.swift

import CoreData

struct PersistenceController {
static let shared = PersistenceController()

static var preview: PersistenceController = {
    let result = PersistenceController(inMemory: true)
    let viewContext = result.container.viewContext
    for _ in 0..<10 {
        let newItem = Item(context: viewContext)
        newItem.timestamp = Date()
    }
    do {
        try viewContext.save()
    } catch {
        // Replace this implementation with code to handle the error appropriately.
        // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        let nsError = error as NSError
        fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
    }
    return result
}()

let container: NSPersistentContainer

init(inMemory: Bool = false) {
    container = NSPersistentContainer(name: "test2CoreData")
    if inMemory {
        container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
    }
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

            /*
            Typical reasons for an error here include:
            * The parent directory does not exist, cannot be created, or disallows writing.
            * The persistent store is not accessible, due to permissions or data protection when the device is locked.
            * The device is out of space.
            * The store could not be migrated to the current model version.
            Check the error message to determine what the actual problem was.
            */
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
}

}

---- MyApp.swift

import SwiftUI

@main
struct myApp: App {
let persistenceController = PersistenceController.shared

var body: some Scene {
    WindowGroup {
        ContentView()
            .environment(\.managedObjectContext, persistenceController.container.viewContext)
    }
}

}

— ContentView

import SwiftUI
import CoreData

struct ContentView: View {
@Environment(.managedObjectContext) private var viewContext

@FetchRequest(
    sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
    animation: .default)
private var items: FetchedResults<Item>

var body: some View {
    List {
        ForEach(items) { item in
            Text("Item at \(item.timestamp!, formatter: itemFormatter)")
        }
        .onDelete(perform: deleteItems)
    }
    .toolbar {
        #if os(iOS)
        EditButton()
        #endif

        Button(action: addItem) {
            Label("Add Item", systemImage: "plus")
        }
    }
}

private func addItem() {
    withAnimation {
        let newItem = Item(context: viewContext)
        newItem.timestamp = Date()

        do {
            try viewContext.save()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
    }
}

private func deleteItems(offsets: IndexSet) {
    withAnimation {
        offsets.map { items[$0] }.forEach(viewContext.delete)

        do {
            try viewContext.save()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
    }
}

}

private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}

@cegrnd Thanks very much for your question!

This is definitely a good suggestion. We always try to ensure that we are as thorough as possible when we publish a book, and this definitely is something very important to include. As the SwiftUI technology is still evolving, eventually we’ll be able to provide better examples that are thorough, and comprehensive to give you the best learning experience.

Please stay tuned, and keep your eyes open for future updates!

@cegrnd Please check out the latest version of the book when you get a chance:

https://www.raywenderlich.com/books/core-data-by-tutorials/v8.0

I hope it helps!

Maybe I’m missing something but the latest version of the book that you linked to says in the getting started section in Ch. 1 that all of the projects in this book use UIKit. Isn’t the OP asking if you could update the Interface to SwiftUI and the LifeCycle to SwiftUI App (both of which are now the default options in XCode)?

As two massive pieces of Apple’s software platform, it won’t surprise you to learn that Core Data and SwiftUI have been written to work well together: we get property wrappers, environment support, and more, all to make sure we can integrate Core Data into our SwiftUI apps with the least hassle.

Before SwiftUI it was common to find a range of ways you might find Core Data being used from an architectural perspective – Apple strongly encouraged us to create containers at the AppDelegate level then reach back up as needed, others preferred using manager classes, and some preferred abstracting Core Data away entirely so they had the freedom to move to Realm or other options at a later date.

However, SwiftUI’s integration with Core Data is different because it points very strongly in one direction: create the Core Data container once when the app starts, inject its managed object context into the environment, then perform fetch requests directly on there.

This isn’t me guessing – Apple literally designed it in a highly specific way, and if you want to take advantage of all the features of SwiftUI’s Core Data integration then you ought to follow the path Apple is laying down for us.

Here are the four specific features that will help you see what I mean:

NSManagedObject conforms to the ObservableObject protocol, which means we can bind any object to part of our user interface.
There’s a managedObjectContext key in the environment designed to store our active Core Data managed object context.
Xcode’s template then injects that context into the initial content view.

So, we create a managed object context when the app launches, attach it to the environment for our views, then use FetchRequest to load data for the app to use.

This may help you,
Rachel Gomez