Easily preview SwiftUI and CoreData
SwiftUI is an amazing new framework to build UIs, and one it’s defining features is Xcode’s previews, which allow you to code a UI live for the first time in iOS; Core Data is also an amazing platform, but a replacement is due, personally I hope they’ll announce a declarative new framework, built for Swift this WWDC 21, but meanwhile this guide will allow you to maximize your productivity using both frameworks.
Here’s a sneak peak of the final syntax you’ll use in this guide:
If you create a project using the SwiftUI App and CoreData template, it works, right-out-of-the-box, but it’s very limited and could encourage bad practices, there’re two in particular that bother me.
The first one is they use a Singleton, to access a `NSManagedObjectContext` in Xcode’s previews, so the UI could be fed with some CoreData objects, this is a bad practice, since nothing is limiting you from accessing the Singleton in production code; in SwiftUI there’s no need to use Singletons, since we have `@EnvironmentObject`.
The second problem is scalability, the `previewingData` is hard-coded inside the `Persistence` Singleton, while this might work for small projects, for larger ones, you can end up having a gigantic Singleton to manage your `previewingData`.
Let’s make a `Previewing` view, that could give us an instance of a `NSManagedObject` and/or a data-rich `NSManagedObjectContext`, and let’s set a clear place to put our previewing objects at, it also allows us to assign any `EnvironmentObject` for free to our Previews.
For existing projects jump to the second step
1st step: Create a Project
Start by creating a new project in Xcode, click Multi-platform, then App, and check the `Use Core Data` checkbox.
This template gives us an `Item` object, that has a `timestamp` attribute; if you go to `ContentView`, you’ll see on the `Preview` has an environment context, that’s referencing that infamous Singleton, this works, but we can do better.
2nd step: Create a new file called “Previewing.swift”
Here, we’ll create a `View` that takes two parameters, a data source using a `KeyPath`, so it feels very Swiftly, and a `@ViewBuilder`, so any view can be previewed; we’ll need two generic types, a `Content` which is of type `View`, and a `Model`, that could be any type, so we could preview arrays of objects or a single object, since we’ll be using `KeyPath` this is completely type-safe. Remember to import Core Data and SwiftUI!
As you can see on the image, the `View` will have two attributes, the `Content` and the persistence manager, this will allow us to pass to the body the `Context` as an `Environment`.
3rd step: Create a Struct called PreviewingData
This is where we’ll declare our previewing data, this will allow us to use `KeyPaths`, to gain type safety and a very nice syntax, it’s preferable to put this struct in another file.
Note that the `items` computed property is a closure that takes in a `Context`, this will be provided by `Previewing` to generate this data and put in on a usable `Context`, here’s where you can add your own data!, simply add it as another computed property; note that this closure returns an Array of `Item`, you can return anything you like, for example a single `Item`, the `KeyPath` will allow to you access it easily: `\.myData`.
If you find that syntax to be weird, the reason behind its that we don’t want to store closures on that Struct, we can to keep it lightweight, so we use a computed property that returns a closure, that’s why there’re two curly brackets.
4th step: Create the initializers
We now need to use `KeyPath` and `@ViewBuilder` to generate the data, and pass it to the `context` and the `content`; an instance of `PersistenceController` is created to avoid using the singleton, you could add multiple `EnvironmentObject` if you need them here, and get them on your previews for free!, this initializer will allow you directly get an instance of the object to work on.
Now, let’s create a second `init` this one will allow us to get a data-rich context, but not the actual data, we’ll call the first parameter `contextWith`, to differentiate the two; you can use `@FetchRequest`.
Now we can access a super simple syntax in our code, to make development super easy. This snippet uses the context with data initializer, useful for `@FetchRequest`. Note that the Production View in this Preview automatically gain access to the context.
This one uses an instance of `Item` so can design our `ItemRowView`.
On PreviewingData we have:
On our View:
For any suggestions or improvement, we can collaborate on this code at: