Persisting Data in CoreData using SwiftUI.
Tags: swift, xcode
Published: 2021-02-14 13:14:42

---
title: Persisting Data in CoreData using SwiftUI.
published: true
description: Persisting Data in CoreData using SwiftUI and Swift 5
tags: swift, xcode
//cover_image: https://dev-to-uploads.s3.amazonaws.com/i/h6cxxchnwdtp5uy6xhgk.png
---

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/h6cxxchnwdtp5uy6xhgk.png)

I've tasked myself with learning Swift, Swift 5 to be exact, though I've tried to replicate Apple's Calendar app using Swift 2 many moons ago which did worked. Things has changed since and now I wanted to add another programming language to my list of programming stack.

##Reintroduction
My name is Daveyon Mayne and I'm a software engineer at [RTC Automotive Solutions](https://www.rtcauto.co.uk/). My core work is working on our in-house software called [CloseIt](https://closeit.co.uk/) for car dealers/dealerships.

Imagine you have a form where your customer fills out long form by taking the information from their customer face-to-face or over the phone or via any other medium and your user accidentally closes the form. All data will be lost where your user now use the phrase "erm… could I take your name, age etc again please?" They've lost the data entered before saving. As good programmers we should always persist the data when any data is entered or removed. Ever seen few apps present you with "Saving draft…"? Yes, as you type, your data is saved into a database somewhere. This could be locally or remotely. I'll be skipping the boring stuff as code if life. 

I'll assume you've created a new project with CoreData selected. If not, "youtube" some videos on how to enable CoreData. I'm using Xcode 12.

```swift
import SwiftUI

struct CreateCustomerView: View {
    @StateObject private var customerForm = CustomerDraftFormFields()

        // We could move this in CustomerDraftFormFields class 
        // and keep our view... clean? 🤷🏽
        let titles = ["Mr", "Miss", "Mrs", "Dr"]


        var body: some View {
            // I'm not using a NavigationView here. You can.
            // You can use Form or List here. I use List because of styling
            List {
                // I use a Group because my List has more than 10 items
                // For this example, you do not need it
                Group {
                    Section {
                        Picker("Title", selection: $customerForm.title, content: {
                            ForEach(titles, id: \.self) {title in
                                Text(title).tag(title)
                            }
                        })
                        
                        TextField("First Name", text: $customerForm.firstName)
                            .disableAutocorrection(true)
                        TextField("Last Name", text: $customerForm.surname)
                            .disableAutocorrection(true)
                    }
                }
            }
        }
}
```

With that little amount of setup, you should have a basic view to work with. You will have some errors so let's fix that. We need a `CustomerDraftFormFields` class:

```swift
import Foundation

class CustomerDraftFormFields: ObservableObject {
    @Published var title: String = ""
    @Published var firstName: String = ""
    @Published var surname: String = ""

    // It looks much better to keep this here.
    let titles = ["Mr", "Miss", "Mrs", "Dr"]
}
```
Let's make a slight update to how we loop the titles:

```swift
[...]

Section {
    Picker("Title", selection: $customerForm.title, content: {
        ForEach(self.customerForm.titles, id: \.self) {title in
            Text(title).tag(title)
        }
    })
    
    [...]
}
```
##Listen For State Change

There needs to be a way to listen for some sort of events when any of our form fields change. To do this, we add `didSet` on each of our fields we are listening for data change.

```swift
class CustomerDraftFormFields: ObservableObject {
    @Published var title: String = "" {
        didSet {
            print(title)
        }
    }
    @Published var firstName: String = "" {
        didSet {
            print(firstName)
        }
    }
    @Published var surname: String = "" {
        didSet {
            print(surname)
        }
    }

    [...]
}
```
Making a change to either fields, we get a print of the value. `didSet` runs after the property was set and `willSet` is.... yes, it runs before the property was set.

##CoreData
I won't be going through the data setup. I'll assume that's already been taken cared of and you have an entity called `DraftCustomers`. Call this what ever you like but for this example, we'll use `DraftCustomers`. Update your class file to import CoreData:

```swift
import Foundation
import CoreData

[...]
```
Now we need to pre-populate the saved values from CoreData to our form fields, if any:

```swift
import Foundation
import CoreData

class CustomerDraftFormFields: ObservableObject {
    // Make reference to our context
    private let viewContext = PersistenceController.shared.container.viewContext
    private let fetchRequest: NSFetchRequest<DraftCustomers> = DraftCustomers.fetchRequest()

    [...]

    // Pre-populate the values on first load.
    init() {
        // Only include the first record in the array.
        // fetchRequest will be an array containing only one record (the first)
        fetchRequest.fetchLimit = 1
        
        do {
            // If there's an error, catch will active
            let customers = try viewContext.fetch(fetchRequest)
            
            if customers.count == 1 {
                title = customers.first?.title ?? ""
                firstName = customers.first?.firstName ?? ""
                surname = customers.first?.surname ?? ""
            }
            
        } catch {
            let error = error as NSError
            fatalError("Could not fetch draft customer: \(error)")
        }
    }
}
``` 
With just that amount of setup, our form fields should be pre-populated. We haven't saved anything as yet so nothing will be shown. Let's save some data. Still in our class:

```swift
[...]

private func saveOrUpdateDraft(column: String, value: Any) {
    fetchRequest.fetchLimit = 1

    do {
        let customers = try viewContext.fetch(fetchRequest)

        if customers.count == 1 {
            let customer = customers[0] as NSManagedObject
            customer.setValue(value, forKey: column)

            // Update the entity record
            try viewContext.save()
        } else {
            let customer = DraftCustomers(context: viewContext)
            customer.setValue(value, forKey: column)

            // Create a new entity record
            try viewContext.save()
        }
    } catch {
        let error = error as NSError
        fatalError("Could not save customer as draft: \(error)")
    }
}
```
We've defined a function called `saveOrUpdateDraft(...)`. The sole purpose of this function is to update a record or create a new record if not found. When you first run this function, it will (should) save the data to our database, but not just yet. Let's update our `didSet`:

```swift
@Published var title: String = "" {
    didSet {
        saveOrUpdateDraft(column: "title", value: title)
    }
}

@Published var firstName: String = "" {
    didSet {
        saveOrUpdateDraft(column: "firstName", value: firstName)
    }
}

@Published var surname: String = "" {
    didSet {
        saveOrUpdateDraft(column: "surname", value: surname)
    }
}

[...]
```

Every time we update our form fields, `saveOrUpdateDraft` will run. In the code we specified the column and its value to be entered (created or updated).

You may have a more complex setup but you get a basic idea on how to save or updating a data if already exists in the database.

##Improvements
It's still my early days in learning Xcode + Swift. Could this be refactored or refactor for less performance hit? Let me know and I'll update where needed.