Core Data Model SwiftUI

Are there any best practices or sample code provided for SwiftUI and Core Data?


Specifically, using a Core Data NSManagedObject as a data provider of a View. Is it a @ObjectBinding? How do you handle the PreviewProvider? If I'm passing in a Core Data object presumably I need a whole context and store coord to instantiate.

Replies

I would also appreciate sample code, as the sample for Core Data and CloudKit is not using SwiftUI.

The most likely recommended strategy is to create a view model for each item you want to pass to your SwiftUI view, along with a datasource class with properites for a PassthroughDataSource, NSFetchedResultsController and an array of view models.


Set your datasource as the results controller delegate and configure your results contrller fetch predicate. Your view model should have an initalizer that takes an NSManagedObject and sets the properties you want to display in your UI. When the delegte method is called, enumerate over the results, create view models initalized with the resulting core data objects, append them to the array, then call send(.self) on your PassThroughSubject.


I usually add a createSampleData() method that populates the same array with hard coded view models initalized with test data for use with the preview provided.


class MyManagedObject: NSManagedObject {
   
    @NSManaged var name: String?
    @NSManaged var age: NSNumber?
}

struct MyViewModel {
   
    var name: String
    var age: String
   
    init(managedObject: MyManagedObject) {
        self.name = managedObject.name ?? ""
        self.age = "\(managedObject.age)" ?? ""
    }
}

final class QueryListStore: NSObject, BindableObject, NSFetchedResultsControllerDelegate {
   
    var didChange = PassthroughSubject<queryliststore, never="">()
    var results = [MyViewModel]()
    var controller = NSFetchedResultsController()
   
    override init() {
       
        super.init()
        /* Create and configure fetched results controller */
       
        controller.delegate = self
       
        do {
            try controller.performFetch()
        } catch {
            fatalError("Failed to fetch entities: \(error)")
        }
    }
   
    func controllerDidChangeContent(_ controller: NSFetchedResultsController) {
       
        var viewModels = [MyViewModel]()
       
        /* Iterate over results, initalizing view models from your managed objects and adding them to the array
        
         let viewModel = MyViewModel(managedObject: managedObject)
         viewModels.append(viewModel)
        
         */
       
        self.results = viewModels
       
        didChange.send(self)
    }
}

Thanks, for your code. I could not figure out, where to integrate QueryListStore.

i’m not that familiar with Core Data but wouldn’t the code example above fault all the managed objects returned in the query And build a ViewModel for each, so only useful for small data sets? Any thoughts on how to do this but keep batch faulting machinism?

That seems to be recommended approach for read-only use cases. But what if you wanted to have a binding to a property in the managed object to be mutated in the UI?

The framework is a mix of Microsoft's MVVM and Facebook's DOM-based React framework.

- Based on the recommended approach (XCode Preview session), you should not mutate the Core-Data Entity.

- Instead, the bindings to the ViewModel triggers update of the UI.

- This also makes sure that your UI is dependent on design time ViewModel (and not run time core-data)


= Better testing, better preview support, better maintenance.


The big unknown is how performant SwiftUI will be in the real word

Good solution.

The changes I would suggest is adding id to the ViewModel to make it unique and using didSet to update the backing store.

There are some examples of using Core Data with SwiftUI showing up on github and the web:


https://github.com/StephenMcMillan/Dub-Dub-Do


https://github.com/italoboss/EmotionalDiary


https://medium.com/@rosscoops/swiftui-nsfetchedresultscontroller-f9f27718e3d4


They seem to work with simple models (one entity), haven't seen any code based on more complex models (multi-entity with relationships) yet, nor have I been able to get a more complex model to work.

Which part doesn’t work on complex entities/joins? I am migrating a very complex app over and am fairly comfortable SwiftUI will Carr for most cases so far, have some unexplored notification and navigation things, plus sig in with Apple etc I still need to work out.


but is it the fact you cannot map the data to a proxy object? Or Is the fetch not triggering update on the nested entities? Or something else, like it just fundamentally “breaks”?


I am just starting to think this one through so starting to work out how to handle this.


My current thinking is 2-fold. The first is part is it appears you need to treat the data preparation and collation very much like redux. But this is of course a concern for large data sets. Out app generally doesn‘t show more than a handful of rows on any one biew a time generally, so thats not really a current concern. It appears the long term goal MAY be for nsfetchedresultscontroller, or a derivative/replacement to offer some layer of direct usage and potentially more support for Combine to cache/etc, or it may already be doing that in the background.

it appears the current concern is the need to translate all the objects into proxy objects which gets us the whole faulting in issue.


Anyone else got some other advice or resources to contribute?

you wouldn’t think it would be too hard right? The array coming back from the nsfetchedresults controller is just an array, and the “swift ui” code is not potentially literally executed in that fashion. It COULD be the internal abstraction in use in the translation essentially uses the closure as a body to render a give cell and it only calls the body when required? Has anyone actually tried creating a List with 40,000 elements and put a print in the closure to see if they are all actually calculated?


surely the abstraction isn’t stupid enough to actually attempt to render all rows?


Yet, without a cell height hint/estimate, then maybe it absolutely must do so for now? Otherwise, how would it now the range of visible cells?

Hi, i’m As well looking forward to seeing the Apple best approach of the CoreData integration with SwiftUI. Anyone of you have found an Apple tutoriel or code on how to do it, properly ? Best Tim

I've just been having problems trying to sort passed through data.

Could your View Model have no storage and simply have getters and setters that connect the view to the core data model doing any processing in real time as the data passes through, thereby avoiding the faulting problem?
EDIT: Well, that doesn't work, List still calls all of them firing all the faults.

Sorry for asking this newbie question. Let's consider a basic contact list app:


Say I have a list of "Row" elements displaying only a photo and First Name. Tapping on a row will take you to a "Detail" page that shows other data like last name and date of birth.


  1. Am I supposed to create two separate viewmodels - one for the "Row" view and another for the "Detail" view?
  2. If so, should I be initialising two separate arrays of these when the app loads (and the dataStore gets initialised in SceneDelegate.swift)?
  3. When a user is in editing mode in the "Detail" page, is the SwiftUI View "allowed' to call methods in the dataStore class directly? It seems very easy to do this with when I can set the dataStore as an @EnvironmentObject variable.


I'm super new to MVVM and would appreciate any form of guidance.

Here is a method which seems to work with Xcode 11 Beta 4. I have created 50000 and displayed them in a List by binding the result of a fetch request without having the 50000 faults being fired.


I use SwiftUI List with the .objectIDproperty as id instead of implementing Identifiable:


List(myStore.arrayOfManagedObjects, id:\.objectID)

Then in my cell View, I fire the fault in onAppear by accessing a property of the NSManagedObject.


My NSManagedObject implements BindableObject so that when it is realized from database (awakeFromFetch), willchange is send and the cell view is refreshed.


It seems to work. Will post code later if requested.