NEW: Start my new Ultimate Portfolio App course with a free Hacking with Swift+ trial! >>

Core Data Abstraction in SwiftUI

Forums > SwiftUI

I know in SwiftUI there are property wrappers to load data from CoreData. But I don't want to distribute the dependencies to the database in all views. So I was wondering how to abstract CoreData? Property Wrapper I can only use one view object.

Does anyone of you have an idea how to achive it?

   

Paul explains in detail how to inject the managed object context into the SwiftUI environment:

https://www.hackingwithswift.com/quick-start/swiftui/how-to-configure-core-data-to-work-with-swiftui

This seems to be how Apple wants us to use the environment. You just grab the environment variable from whichever view you want. Other views might not know about the variable at all. I would not necessairly call it distributing dependency to all views. Would you?

   

I know that, but is there another way? I'd like to have "normal" object in my business logic not ManagedObjects

   

I was under impression that ManagedObjects are part of Core Data stack. Therefore, you would need them regardless if you use SwiftUI or UIKit. Can't think of any other way. Sorry.

   

It is certainly possible to abstract coreData away. I started down this road in my project, but I reverted to using @FetchRequest because that very nicely responds to changes, and I assume it is much more efficient than the approach I was taking.

I'm not here to talk you out of abstracting though! I'll show you what I did and let you make the decision.

You can create an ObservedObject that uses CoreData to save and fetch objects.

public class ItemService: ObservableObject {
    @Published var itemList: [MyItem] = []

    private let context: NSManagedObjectContext
    private var managedItems: [ManagedItem]

    init(context: NSManagedObjectContext) {
        self.context = context
        self.itemList = try? context.fetch(NSFetchRequest<ManagedItem>(entityName: "Item")) ?? []
    }

    func createItem(item: MyItem) {
        let newManagedItem = ManagedItem(context: self.context)
        newManagedItem.property1 = item.property1 // etc...
        try? self.context.save()
        applyChanges() // this function should refetch data and assign it to the published itemList
    }
}

This service can then be injected as an EnvironmentObject that views can use to access the list, make changes, etc.

Like I said, there are probably efficiency problems here. I was refetching the entire list any time there was an indication that the list changed.

   

@Pyroh  

What I usualy do to avoid manipulating managed objects when it's not convenient —temporary objects, cancelable updates, etc...—  is creating beside the NSManagedObject subclass:

  • a protocol that defines the fields of the object
  • a structure that act as an object proxy
  • a bunch of conversion methods.

Let's get our old friend Person. You define a CoreData entity with :

  • firstName of type String
  • lastName of type String
  • birthDate of type Date

The protocol could be :

protocol PersonType {
  var firstName: String { get set }
  var lastName: String { get set }
  var birthDate: Date { get set }
}

The proxy structure :

struct PersonProxy: person  {
  var firstName: String
  var lastName: String
  var birthDate: Date

  init(proxying object: Person) {
    self.firstName = object.firstName ?? ""
    self.lastName = object.lastName ?? ""
    self.birthDate = object.birthDate 
  }
}

Finally a Person extension will provide a proxy -> managed object conversion method and another create a new object

extension Person {
  func update(from proxy: PersonProxy) {
    self.firstName = proxy.firstName
    self.lastName = proxy.lastName
    self.birthDate = proxy.birthDate
  }

  static func create(from proxy: PersonProxy, inContext ctx: NSManagedObjectContext) -> Self {
    let record = self.init(context: ctx)

    record.firstName = proxy.firstName
    record.lastName = proxy.lastName
    record.birthDate = proxy.birthDate

    return record
  }
}

   

@Pyroh thank you! However, why include PersonType / what purpose does the protocol serve?

   

What you are asking for is the idea behind MVVM (Model, View, View-Model) instead of MVC. Shown above you build a model that supports your view structure. Then feed it Coredata model. So, it translates the "business rules" represented by your coredata model into view/display rules used by your views. 

I suggest reading some on MVVM design. Here is a tutorial on those ideas and Combine: https://www.raywenderlich.com/4161005-mvvm-with-combine-tutorial-for-ios

   

I was asking myself the same question, this twitter thread has a lot of useful info: https://twitter.com/donnywals/status/1280173379966861313?s=21

   

Hacking with Swift is sponsored by Instabug

SPONSORED Catch bugs as soon as they happen and know exactly why a crash occurred. Instabug's SDK grabs all the logs they need to fix bugs, crashes and performance issues in minutes instead of days. Get screenshots, device details, network logs, repro steps, and tons of other critical insights needed to resolve issues and prioritize product backlogs straight from your dashboard. It only takes a minute to integrate!

Get started now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.