Combine CoreData and SwiftUI
Tags: swift, swiftui, coredata, ios
Published: 2023-06-09 15:26:15

Recently I had to build an app using **SwiftUI** and **CoreData**.
I thought CoreData was used almost as you would use it with UIKit, but apparently there are some differences.
This guide is intended as a summary of my experience using CoreData combined with SwiftUI. If I will find other aspects, I will add them to this guide.
So, without further ado, let's begin.
## Set-up
This is a pretty straightforward part.
* If you start a new project you can check the '**Use Core Data**' option and '**Host in CloudKit**' if you want to save your users data to the cloud. In this case XCode will take care of setting up the project for you. (For the CloudKit part will need to do some extra steps).
* If you have already a project you'll need to create a **Persistence.swift** file and a swift file where you initialise the CoreData stack. (You can even create a new project following the previous step, and copy the **Persistence.swift** file generated by XCode to your project.

### Setup the @main app
Now that you have your project, to be able to use CoreData within your `View`s, you need to pass the `managedObjectContext` down you view hierarchy.
Usually this is done in your **<AppName>App.swift** file.
```swift
@main
struct ExampleApp: App {
 
  let persistenceController = PersistenceController.shared
 
  var body: some Scene {
    WindowGroup {
      ContentView()
        .environmentObject(persistenceController)
        .environment(\\.managedObjectContext, persistenceController.container.viewContext)
    }
  }
}
```
Personally I pass, as an `.environmentObject`, the **PersistenceController** itself. Because I like to keep all the CRUD logic inside that struct, but that's up to you. If you want to do so you need to make your **PersistenceController** struct conform to **ObservableObject**.
Great, now you're able to create, read, update and delete `NSManagedObjects` in your `View`s.
> **TIP**: **PersistenceController** ([here is an exemple](https://blog.leonifrancesco.com/articles/persistence-controller)) is the struct that XCode generates  automatically for you when you start a new project with the '**Use Core Data**' option checked.

## Operating with CoreData
To operate with CoreData in your `View` you need to access the `NSManagedObjectContext`. To do so, you have two options:
* Using the **@Environment** wrapper in your `View`s
```swift
@Environment(\\.managedObjectContext) private var viewContext
```
* Using the **@EnvironmentObject** wrapper to get your controller, and from it you can access the `viewContext` or CRUD methods
```swift
@EnvironmentObject private var persistenceController: PersistenceController
```
## Saving the context
You can save the context using the `.save()` method of `NSManagedObjectContext`. Before saving the context you can check whether  there are some changes, and save it only if they are present.
```swift
do {
  if container.viewContext.hasChanges {
    try container.viewContext.save()
  }
} catch {
  print(error)
}
```
## Saving objects
To save a `NSManagedObject` you first need to instanciate it and than configure its properties. And then save the context.
```swift
let note = Note(context: container.viewContext)
note.id = id
note.text = text
note.folder = folder
note.creationDate = Date()
saveContext()
```
> **TIP**: You can animate this insertion wrapping this code inside the `withAnimation` function of SwiftUI.

## Fetching objects
Now the fetching part, this is where I found the most difficulties. Let's start right away.
To fetch objects in SwiftUI the most convenient way of doing it is using the **@FetchRequest** or **@SectionedFetchRequest**  wrappers, in every single `View` you need to read from CoreData.
> **WARNING**: Passing fetched objects between `View`s will break the automatic updates in case you add, edit or delete objects. (If you know a way to pass fetch objects without breaking the updates let me know and I will update this guide)

So you need to add a **@FetchRequest** in every `View` you need CoreData objects and automatic updates. I know it is a bit annoying but it will be worth it.
So, with that said the code is the following:
```swift
@FetchRequest(entity: Object.entity(),
              sortDescriptors: [NSSortDescriptor],
              predicate: NSPredicate,
              animation: .default)
var objects: FetchedResults<Object>
```
Or, if you want your objects grouped by a property:
```swift
@SectionedFetchRequest(entity: Object.entity(),
                       sectionIdentifier: \\Object.property,
                       sortDescriptors: [NSSortDescriptor],
                       predicate: NSPredicate,
                       animation: .default)
var sections: SectionedFetchResults<YourSectionType, Object>
```
To use these sections you just pass them to a `List` or `ForEach`, and then to another `ForEach`.
```swift
List(sections) { section in
  Section(section.id) {
    ForEach(section) { object in
      // Configure your view with the object
    }
  }
}
```
> **TIP**: If you use **@SectionedFetchRequest** with sorting you may need to specify two sort descriptor. The first will sort the sections and the second one will take care of the objects inside each section. This feature is really useful and requires very little effort.

Using **@FetchRequest** or **@SectionedFetchRequest** wherever you add, update or delete an object, the views are automatically updated.
Now, if you need to use a **@FetchRequest** with a `NSPredicate` that has a parameter passed from the parent `View`, I found that the next option works wonderfully.
```swift
@FetchRequest
var objects: FetchedResults<Object>
 
init(id: ID) {
  _objects = FetchRequest<Object>(predicate: NSPredicate(format: "id == %@", id))
}
```
This way you can pass your parameter in the `init` and use it to filter your results while keeping the automatic updates. The same goes for **@SectionedFetchRequest**.
## Updating objects
To update a `NSManagedObject` you will need to have the `NSManagedObject` to update, fetched as described before and update its properties. And then save the context.
```swift
note.text = newText
note.folder = newFolder
saveContext()
```
## Deleting objects
To delete a `NSManagedObject` you will need to have the `NSManagedObject` to update, fetched as described before and delete it.
```swift
viewContext.delete(object)
```
------

I didn't explained every single CRUD method of CoreData like batch insertion and deletion since this guide is focused on the interaction between CoreData and SwiftUI. Every `NSManagedObjectContext` method, background context, performAndAwait, … behaves exactly like it does with UIKit.

------
## Conclusion
That's all I have discovered so far about **CoreData** used with **SwiftUI**. This guide will be constantly updated.
If you want to suggest a better way of doing something, **leave a comment** and I will update this guide with the best option.
I hope I helped you with you journey with SwiftUI and iOS development.
See you in the next guide!

------
If you would like to support my work and see this guide in action check out [BrainDump - Notes & Writing](https://apps.apple.com/us/app/braindump-notes-writing/id6448230631)
Thank you 🙏

------

## SwiftyLion
This article was originally published on [**SwiftyLion**](https://blog.leonifrancesco.com/articles/coredata-with-swiftui). Head over there if you like this post and want to read others like it.