Leave a rating/review
In the last video you created a managed object subclass for the RocketLaunch entity. Now you can use this class to create instances of a RocketLaunch for users to save. If you build and run the app, you’ll see a new rocket launch button at the bottom of the screen. When you tap on that you’re presented with a modal view to enter details about the rocket launch.
Back in the codebase this modal view is represented by the LaunchCreateView type and this is where you’ll add code to save a RocketLaunch. Near the bottom of that view you should see a Button with the text “Save”. All this button is doing right now is dismissing the modal view when tapped. Above the dismiss() call on the presentationMode let’s create an instance of RocketLaunch.
RocketLaunch(
When you type out the open parenthesis to create an instance you’ll see that there are a couple initializers you can use. The first one takes a managed object context, and the second one an entity description and a context. As mentioned earlier all managed objects need to be inserted into the context so that it can keep track of all the changes. That’s what’s happening here when you pass in the context. The designated initializer handles registering the instance you create with the context.
But where do you obtain the context from? Earlier you created a core data stack using the NSPersistentContainer class - let’s use that to obtain a managed object context. As we discussed earlier the context is the only part of the stack you’re going to be exposing to the rest of our app.
Navigate to RocketLaunchesApp and here you can see that an instance of ContentView is being created and assigned as the main view of the WindowGroup which is what is presented when the app starts.
If you’ve worked with SwiftUI before you should be familiar with the environment object which is a property wrapper for an observable object that lets us share data around the app. For any given view an environment object must be supplied by its parent so if we take that logic all the way back the ancestor view of all the views in the app, the ContentView in this case, must supply the environment object.
Recall that when you made the PersistenceController there were two static properties to fetch a controller from anywhere in your app. Add the following to the top of the RocketLaunchesApp struct:
let persistenceController = PersistenceController.shared
Now that you have access to the persistence controller we can place its context into the environment using the environment modifier and \.managedObjectContext key path:
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
Great! You now have a context that you can use elsewhere in the app.
Navigate back to the LaunchCreateView and import Core Data.
import CoreData
You can now access the managed object context by using the @Environment property wrapper along with the key path to access the value on the environment object.
@Environment(\.managedObjectContext) var viewContext
Now that you have the context you can use it to create an instance of RocketLaunch.
let launch = RocketLaunch(context: self.viewContext)
You can now set values on this instance and because the storage of these properties are managed by Core Data the context will keep track of it for you.
launch.name = self.name
launch.notes = self.notes
launch.launchDate = self.launchDate
launch.isViewed = self.isViewed
launch.launchpad = self.launchpad
This feels very much like assigning values to any other instance you work with in Swift and that’s intentional. If this were overly cumbersome most people wouldn’t use Core Data. But there is no real backing instance. Remember we just inserted a new RocketLaunch entity into the context and its now keeping track of the values it eventually needs to hand off to the persistence store.
These values are not persisted until you call save on the context, so let’s go ahead and do that. The managed object context defines a save() method that you can call but it’s a throwing one so you need to use a do catch clause.
do {
try self.viewContext.save()
}
If this fails you’ll just log the error and crash the app. If this were a real app you would need to provide proper error handling to let the user know something went wrong.
catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
Now when you tap on the save button the RocketLaunch will actually be saved to the persistent store! Pretty cool huh? Before you test this out, let’s clean up the code a bit. What you just wrote here - creating a RocketLaunch and assigning values to the instance, that’s a common bit of code that you can refactor into a method and automatically save. Select all the code you just added and hit command+c to copy.
Navigate to RocketLaunch+CoreDataProperties and add a static method
static func createWith(
name: String,
notes: String,
launchDate: Date,
isViewed: Bool,
launchpad: String,
using managedObjectContext: NSManagedObjectContext
) {}
Now paste the copied code in here using Cmd+V. You’ll need to make a couple of minor changes and remove all the self calls and change from viewContext to mangedObjectContext.
Now back in LaunchCreateView, you can replace the entity code with your new method.
RocketLaunch.createWith(
name: self.name,
notes: self.notes,
launchDate: self.launchDate,
isViewed: false,
launchpad: self.launchpad,
using: self.viewContext)
Let’s test this out! Build and run the app…
Tap on the new Rocket Launch button at the bottom to create a new RocketLaunch. Give it a title
A really cool launch
a launchpad
My back yard
and some notes
I really hope this takes off!
Tap on save and if it doesn’t crash that means you’re good to go!
When the modal is dismissed and you land back on the list view you still see the test data. In part two of this course, you’ll fix this by fetching RocketLaunches from the persistent store! Before heading there, let’s recap what you’ve learned in this course, which is the next video.