30

Preview canvas is is crashing but in simulator everything working fine. I assuming it related to @ObservedObject and @Fetchrequest...

tried solution for here Previewing ContentView with CoreData

doesn't work

 import SwiftUI
 import CoreData

struct TemplateEditor: View {

@Environment(\.managedObjectContext) var managedObjectContext

@FetchRequest(
    entity: GlobalPlaceholders.entity(),
    sortDescriptors: [
        NSSortDescriptor(keyPath: \GlobalPlaceholders.category, ascending: false),
    ]
) var placeholders: FetchedResults<GlobalPlaceholders>


@ObservedObject var documentTemplate: Templates
@State private var documentTemplateDraft = DocumentTemplateDraft()
@Binding var editing: Bool


var body: some View {

    VStack(){
        HStack(){
            cancelButton
            Spacer()
            saveButton
        }.padding()
        addButton
        ForEach(placeholders)  {placeholder in
            Text(placeholder.name)
        }
        TextField("Title", text: $documentTemplateDraft.title)

        TextField("Body", text: $documentTemplateDraft.body)
            .padding()
            .frame(width: 100, height:400)
        Spacer()
    }

...




}
struct TemplateEditor_Previews: PreviewProvider {
    static var previews: some View {

    let managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Templates")
    request.sortDescriptors = [NSSortDescriptor(keyPath: \Templates.created, ascending: false)]
    let documentTemplate = try! managedObjectContext.fetch(request).first as! Templates


    return TemplateEditor(documentTemplate: documentTemplate, editing: .constant(true)).environment(\.managedObjectContext, managedObjectContext).environmentObject(documentTemplate)

    }
}

Expected to generate preview

6
  • Wat is the error? Commented Sep 4, 2019 at 1:42
  • That's the fun part. Preview canvas never give an error details... just the line that it crashed Commented Sep 4, 2019 at 2:52
  • True enough. You can replace ObservedObject with EnvironmentObject. Does it crash if you remove the @FetchRequest and it's related ui? Otherwise you can try the new master details SwiftUI template in Xcode and add it to a preview and compare the differences. Commented Sep 4, 2019 at 2:59
  • Any news here? I am having the issue too. I use a Realm (www.realm.io) database and the canvas crash when I introduce an Environment Object that use this databse. Commented Sep 9, 2019 at 12:06
  • I can’t make it work. Separate from preview code generating view perfectly. All code complete with no errors too. But preview canvas just failing without providing any specific of an error. For now I have to other choice but to comment it and rebuild project every time I want to see progress. Commented Sep 10, 2019 at 18:39

10 Answers 10

42

I'm not sure if your try line will work if there is no data.

let documentTemplate = try! managedObjectContext.fetch(request).first as! Templates

To get mine to work I created a test Item to use. Like this:

struct DetailView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
//Test data
        let newEvent = Event.init(context: context)
        newEvent.timestamp = Date()
        return DetailView(event: newEvent).environment(\.managedObjectContext, context)
    }
}

I've also noticed that I needed the .environment(.managedObjectContext, context) code in an earlier tabView that hosted the CoreData views or the preview would fail.

Sign up to request clarification or add additional context in comments.

3 Comments

The above question has been asked and answered several different ways. This is the only answer that works! The essence of the problem is that Preview seems to start with it's own (empty!) persistent store, so you must somehow populate that store with enough objects for all of your Previews to work. I created a class function that populates the database with sample objects if the database is empty. For each Entity in my model I also created a class functions that will return one of those sample objects to pass as a parameter as needed for the specific View being previewed.
"I'm not sure if your try line will work if there is no data." is CORRECT. I've tested this by fetching records for an entity, which works when the entity contains one or more existing records. However, when I delete the "test" data for that entity (or more easily the SQLite files) no data displays (perhaps obviously), unless I adopt the solution in this answer and create an entity record to display in the preview
FWIW: This article explains how to debug live preview code (Breakpoints etc.). developer.apple.com/news/?id=8vkqn3ih
13

This answer seems to work in my recent project by replacing the default ContentView_Previews struct, though others are questioning whether it pulls persistent data. Credit goes to @ShadowDES - in the Master/Detail template project in Xcode Beta 7

I'm able to CRUD anything using Canvas (XCode Version 11.3 (11C29)) and it seems to run flawlessly.

    #if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return ContentView().environment(\.managedObjectContext, context)
    }
}
#endif

Add Values, mark priority

Swipe to delete

Deleted!

1 Comment

it is unbelievable how Apple deliverers a preview module that is unable to preview. Does not matter what the problem is. A preview should preview. Xcode is zero stars.
13

What works for me:

I create all of my sample data in the preview property of my persistence controller, building off of the template generated by Xcode when starting a project with the following settings: Interface - SwiftUI, Lifecycle - SwiftUI App, Use Core Data, Host in CloudKit. I have posted the template here:

import CoreData

struct PersistenceController {
    static let shared = PersistenceController()

    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext

        // ** Prepare all sample data for previews here ** //

        for _ in 0..<10 {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
        }
        do {
            try viewContext.save()
        } catch {
            // handle error for production
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()

    let container: NSPersistentCloudKitContainer

    init(inMemory: Bool = false) {
        container = NSPersistentCloudKitContainer(name: "SwiftUISwiftAppCoreDataCloudKit")
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // handle error for production
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
    }
}

In my preview, I inject the persistence controller into the preview environment and for my view argument I use the registeredObjects.first(where:) method on the preview viewContext to pull the first object of the desired type:

struct MyView_Previews: PreviewProvider {
    static var previews: some View {
        MyView(item: PersistenceController.preview.container.viewContext.registeredObjects.first(where: { $0 is Item }) as! Item)
            .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

Edited 11/15/21

Persistence

    import CoreData
    
    struct PersistenceController {

        static let shared = PersistenceController()
    
        static var preview: PersistenceController = {
            let result = PersistenceController(inMemory: true)
            let viewContext = result.container.viewContext
            Seed().prepareData(for: viewContext)
            return result
        }()
    
        let container: NSPersistentCloudKitContainer
    
        init(inMemory: Bool = false) {
            container = NSPersistentCloudKitContainer(name: "SwiftUISwiftAppCoreDataCloudKit")
            if inMemory {
                container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
            }
            container.loadPersistentStores(completionHandler: { (storeDescription, error) in
                if let error = error as NSError? {
                    // handle error for production
                    fatalError("Unresolved error \(error), \(error.userInfo)")
                }
            })
        }
    }

    struct Seed {
        func prepareData(for viewContext: NSManagedObjectContext) {
            // ** Prepare all sample data for previews here ** //

            for _ in 0..<10 {
                let newItem = Item(context: viewContext)
                newItem.timestamp = Date()
            }
            do {
                try viewContext.save()
            } catch {
                // handle error for production
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }

Item Preview

    struct ItemView_Previews: PreviewProvider {
    
        static let persistence = PersistenceController.preview
    
        static var item: Item = {
            let context = persistence.container.viewContext
            let item = Item(context: context)
            item.timestamp = Date()
            return item
        }()
        
        static var previews: some View {
            ItemView(item: item)
                .environment(\.managedObjectContext, persistence.container.viewContext)
        }
    }

6 Comments

Your solution was working after you posted it. But with Xcode 13.1 and iOS 15, it crashs. It says Unexpectedly found nil while unwrapping an Optional value. Like if there is no object in PersistenController's preview. I've found that commenting in PersistenceController's preview the do { ... } catch { ... } makes it work again. But I can't figure out why!
Hi Jonathan is the object in your view initializer optional or required?
It is required. But the only thing that changed with your code is that do { ... } catch { ... } in static var preview. Strange ...
That makes sense, the solution I provided will crash in the case that there are no items saved in the context, if the item is non-optional. The do catch block is catching the nil value before it crashes. You can protect against the edge case of no items in the context by either checking that an item is present before passing to the initializer or making it optional...
Yes but saving items in the context is done in your do { ... }´: try viewContext.save()`. So why 1. it does not save and crashs when this line is uncommented. 2. it works when this line is commented
|
2

So, if you put some code in an onAppear handler in the preview, it will run on boot. It even live updates as you type!

struct TemplateEditor_Previews: PreviewProvider {
  static var previews: some View {
    TemplateEditor().environment(\.managedObjectContext, AppDelegate.viewContext).onAppear {
      let entity = GlobalPlaceholders(context: AppDelegate.viewContext)
      entity.name = "abc123"

      // Or create more, if you need more example data

      try! AppDelegate.viewContext.save()
    }
  }
}

Note that I've wrapped up my viewContext in a static method on AppDelegate to make access a tiny bit less verbose and easier to remember:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  static var persistentContainer: NSPersistentContainer {
    return (UIApplication.shared.delegate as! AppDelegate).persistentContainer
  }

  static var viewContext: NSManagedObjectContext {
    return persistentContainer.viewContext
  }

Comments

1

Works for SwiftUI 2 app using the App template

I also had the previews crash and none of the other solutions were suitable or worked for me.

What I did was rather than the following:

struct ContentView_Previews: PreviewProvider {
  
    static var previews: some View {
        return ContentView()
            .environment(
                \.managedObjectContext,
                CoreDataManager.context
            )
    }
}

I fixed it with:

struct ContentView_Previews: PreviewProvider {
    
    static var previews: some View {
        let context = CoreDataManager.context

        /* Optional sample data can be inserted here */
        
        return ContentView()
            .environment(
                \.managedObjectContext,
                context
            )
    }
}

Where CoreDataManager is:

enum CoreDataManager {
    
    static var context: NSManagedObjectContext {
        persistentContainer.viewContext
    }
    
    static let persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "MyContainerName")
        
        container.loadPersistentStores { description, error in
            guard let error = error else { return }
            fatalError("Core Data error: '\(error.localizedDescription)'.")
        }
        
        return container
    }()
}

Not exactly sure why this helped, but now it works perfectly. Additionally you can add sample data to this context where I have marked with a comment.

Comments

1

This is my solution.

I don't want use CoreData in view. I want MVVM style. So you need to mock Core data for display in Canvas view.

This is an example :

// View
struct MyView: View {
    @ObservedObject var viewModel: PreviewViewModel
}

// View Model
final class MyViewModel: ObservableObject {
   @Published var repository: RepositoryProtocol // CoreData
}

// Repository
protocol RepositoryProtocol { }
class Repository: RepositoryProtocol { ... } 
class MockRepository: RepositoryProtocol { ... } // Create a Mock


// Init of your view
// If Canvas use mock
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
    repository = MockRepository() 
    
// else App use Repository
} else {
    repository = Repository.shared
}

let viewModel = MyViewModel(repository:repository)
MyViewModel(viewModel: viewModel)

Comments

1

This worked for me. In the AppDelegate create a different preview context and fill it with objects.

    lazy var persistentContainerPreview: NSPersistentContainer = {
    let persistentContainer = NSPersistentContainer(name: "MyModel")
    
    persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {

        }
    })

    let didCreateSampleData = UserDefaults.standard.bool(forKey: "didCreateSampleData")
    if !didCreateSampleData {
        let context = persistentContainer.viewContext
        let recipe = Recipe(context: context)
        recipe.title = "Soup 2"
        recipe.difficultyName = "NOT TOO TRICKY"
        recipe.difficultyValue = 1
        recipe.heroImage = "dsfsdf"
        recipe.ingredients = "meat"
        recipe.method = "sdcsdsd"
        recipe.published = Date()
        recipe.recipeId = 1
        recipe.servings = 4
        recipe.tags = "sdfs"
        recipe.totalTime = 100
        recipe.totalTimeFormatted = "Less than 2 hours"
        try! context.save()
    }

    return persistentContainer
}()

Then in your preview.

struct RecipeView_Previews: PreviewProvider {

static var previews: some View {
    
    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainerPreview.viewContext
    let recipe = try! context.fetch(Recipe.fetchRequest()).first as! Recipe
    RecipeView(recipe: recipe).environment(\.managedObjectContext, context)
}

}

Comments

0

One option is to NOT use Core Data in previews. This is helpful enough to see the UI of what I'm building but I'll still need to use Simulator to test the functionality.

#if !DEBUG
// Core Data related code e.g. @FetchRequest
#endif

What was suggested in Previewing ContentView with CoreData worked for me, Xcode Version 11.0 (11A419c) Mac OS 10.15 Beta (19A558d). My crash logs showed an index error,

*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty NSArray'

because there was no data there, so I had to handle this unique "preview" case and that got things working.

1 Comment

It's been some time, what have you ended up doing for previews?
0

It crashes because it was instructed so in the PersistenceController:

struct PersistenceController {
    ...
    static var preview: PersistenceController = {
        ...
        do {
            try viewContext.save()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()
    ...
}

So the actual reason can be seen in the crash report. Actually, XCode 12.4 shows a warning about checking the crash report; however, the report is too verbose for a newbie like me from web development. Thus it took me a while to find out the problem, so I hope this would save some time for others.

...and the problem in my case was a required attribute was not set while populating the core data model for previews.

Comments

0

The thing is you need to find out which line cause the crash.

Since the canvas doesn't show the detailed error, using OSLog and Console.app to debug would be a possible solution.

For example:

import os.log

struct YourView_Previews: PreviewProvider {
    static var previews: some View {
        os_log("[DEBUG]-\(#function)---1--")

        let moc = PersistenceController.preview.container.viewContext

        os_log("[DEBUG]-\(#function)---2--")

        let item = Item.previewData(context: moc)

        os_log("[DEBUG]-\(#function)---3--")

        return YourView(item: item, now: Date())
            .environment(\.managedObjectContext, moc)
    }
}

Remember to use filter to better catch the debug message from Console.

After finding out which line cause the crash, you can further look into the line and continue the process until you find the culprit.

(In my case, I forgot to add UUID to the preview data which causing the canvas to crash.)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.