Search code examples
core-dataswiftui

SwiftUI PreviewProvider using CoreData to show specific NSManagedObject Entry


[Xcode 12.4, Catalina 10.15.6, MacOS Project using SwiftUI and CoreData]

I'm working on an app to display some data I have. The business logic of the app is working fine, and now I've turned my attention to the UI. In order to tidy up the UI, I would like to get the PreviewProvider working for each view. I would also like the previews to display data I already have in the CoreData datastore.

After much frustration, I finally managed to get the preview to compile and display, however I'm not seeing any actual data in the preview. It appears that whilst the NSManagedObjectContext is there, the preview has not selected any entry to display.

How can I select, say the 3rd entry in my datastore to be displayed in the preview?

Below is my code for the view that includes the PreviewProvider at the end (as normal) with some comments explaining it following.

    
    
    import SwiftUI
    
    struct DomainRow: View {
      
      @Environment(\.managedObjectContext) var context
      @ObservedObject var domain : DomainFiles
      private var id : LocalizedStringKey {
        get {
          return LocalizedStringKey(String(Int(domain.id)))
        }
      }
      
      var body: some View {
        HStack (alignment: .center) {
          Text(id)
          Spacer()
          VStack(alignment: .leading) {
            Text(domain.name!)
              .fontWeight(.bold)
              .truncationMode(.tail)
              .frame(minWidth: 20)
    
            Text(domain.path!)
              .font(.caption)
              .opacity(0.625)
              .truncationMode(.middle)
          }
        }
        .padding(.vertical, 4)
      }
    }
    
    #if DEBUG
    struct DomainRow_Previews: PreviewProvider {
    
      @Environment(\.managedObjectContext) var managedObjectContext
    
      static var previews: some View {
        let context = CoreDataStack.context
        let domain = DomainFiles(context: context)
        return DomainRow(domain: domain)
          .environment(\.managedObjectContext, CoreDataStack.context)
      }
    }
    #endif

DomainFiles is a CoreData entity (with a couple thousand entries), each of which has several attributes that include id, name, and path (to the file). CoreDataStack.context is a reference to the struct shown below.

In order to get a static NSManagedObjectContext, as required by the PreviewProvider, I have added the following struct to my project, as per https://developer.apple.com/forums/thread/650269

    import Foundation
    import CoreData
    
    struct CoreDataStack {
        static var context: NSManagedObjectContext {
            return persistentContainer.viewContext
        }
      
        static let containerName: String = "MyAppsDataStore"
    
        static var persistentContainer: NSPersistentContainer = {
            let container = NSPersistentContainer(name: containerName)
            container.loadPersistentStores { (description, error) in
                if let error = error {
                    print(error)
                }
            }
            return container
        }()
    //    Don't need to save anything in the preview.
    //    func saveContext() { ... }
    }

I would, for example, like to pass in the entry with domain.id == 3 into the preview. How can I do this? (Thanks in advance for your attention.)


Solution

  • I worked out what was missing in my PreviewProvider struct:

    1. I needed to perform a NSFetchrequest in order to get the data.
    2. Then, I was able to access entries in the datastore using the array index.
      Note that the index is a CoreData internal representation, and if you want a specific item, use SQLite to browse the datastore to find the one you want to see.
    struct EntriesView_Previews: PreviewProvider {
    
      @Environment(\.managedObjectContext) var managedObjectContext
    
      static var previews: some View {
        let context = CoreDataStack.context
        let domainFiles : [DomainFiles]
        let requestDomainFiles : NSFetchRequest<DomainFiles> = DomainFiles.fetchRequest()
        do {
          domainFiles = try context.fetch(requestDomainFiles)
        } catch {
          let nserror = error as NSError
          fatalError("Error \(nserror): \(nserror.userInfo)")   //  DO NOT DO THIS IN REAL CODE.  PROVIDE FALLBACK
        }
        let domain = domainFiles[0]
        return EntriesView(domain: domain)
          .environment(\.managedObjectContext, CoreDataStack.context)
      }
    }