SwiftUI预览画布和核心数据

时间:2019-09-03 19:48:39

标签: core-data swiftui

预览画布崩溃,但是在模拟器中一切正常。我假设它与@ObservedObject和@Fetchrequest有关...

此处Previewing ContentView with CoreData的尝试解决方案

不起作用

 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)

    }
}

预期生成预览

10 个答案:

答案 0 :(得分:5)

什么对我有用:

我在持久性控制器的预览属性中创建了所有示例数据,使用以下设置启动项目时由 Xcode 生成的模板构建:界面 - SwiftUI、生命周期 - SwiftUI 应用程序、使用核心数据、主机在 CloudKit 中。我已经在这里发布了模板:

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)")
            }
        })
    }
}

在我的预览中,我将持久性控制器注入到预览环境中,对于我的视图参数,我使用 preview viewContext 上的registeredObjects.first(where:) 方法来拉取第一个对象所需类型:

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)
    }
}

答案 1 :(得分:4)

我不确定如果没有数据,您的try线是否可以工作。

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

为了使我的工作正常,我创建了一个要使用的测试项目。像这样:

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)
    }
}

我还注意到,在托管CoreData视图的更早版本的tabView中,我需要.environment(.managedObjectContext,context)代码。否则预览将失败。

答案 2 :(得分:1)

一种选择是不在预览中使用CoreData。这对查看我正在构建的UI很有帮助,但是我仍然需要使用Simulator来测试功能。

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

Previewing ContentView with CoreData中的建议对我来说是XCode 11.0版(11A419c)Mac OS 10.15 Beta(19A558d)。我的崩溃日志显示索引错误,

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

因为那里没有数据,所以我不得不处理这个独特的“预览”案例,并且使事情正常进行。

答案 3 :(得分:1)

这个答案似乎可以通过替换默认的ContentView_Previews结构在我最近的项目中使用,尽管其他人对此表示怀疑是否会提取持久数据。感谢@ShadowDES-在Xcode Beta 7的Master / Detail模板项目中

我可以使用Canvas(XCode版本11.3(11C29))对任何内容进行增删改查,而且它似乎可以完美运行。

    #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!

答案 4 :(得分:1)

使用App模板的SwiftUI 2应用程序

我也遇到了预览崩溃,其他解决方案都不适合我。

我所做的不是以下内容:

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

我用以下方法修复了它:

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
            )
    }
}

CoreDataManager在哪里:

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
    }()
}

不确定为什么这样做有帮助,但是现在可以正常使用了。此外,您可以将示例数据添加到我已标记为注释的context上。

答案 5 :(得分:1)

这是我的解决方法。

我不想在视图中使用CoreData。我想要MVVM样式。 因此,您需要模拟Core数据以在Canvas视图中显示。

这是一个示例:

// 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)

答案 6 :(得分:0)

因此,如果您在预览的//event to open the create customer form private void OpenCreateCustomerForm(){ //first, create an instance of the form frmCreateCustomer frm = new frmCreateCustomer(); /*second, add the function DataFill() to the FormClosed event of the create customer form, so everytime you close it, the MainForms datagridview will get updated*/ frm.FormClosed += new FormClosedEventHandler((object s, FormClosedEventArgs f) => { DataFill();}); //finally, show the create customer form frm.ShowDialog(); }; 处理程序中放入一些代码,它将在启动时运行。输入时它甚至可以实时更新!

onAppear

请注意,我已将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() } } } 封装在viewContext上的静态方法中,以使访问变得更简洁,更容易记住:

AppDelegate

答案 7 :(得分:0)

它崩溃了,因为它在 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
    }()
    ...
}

所以实际原因可以在崩溃报告中看到。实际上,XCode 12.4 显示了有关检查崩溃报告的警告;但是,对于像我这样的 Web 开发新手来说,该报告太冗长了。因此我花了一段时间才找出问题所在,所以我希望这可以为其他人节省一些时间。

...我的问题是在填充核心数据模型进行预览时没有设置必需的属性。

答案 8 :(得分:0)

这对我有用。在 AppDelegate 中创建一个不同的预览上下文并用对象填充它。

    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
}()

然后在您的预览中。

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)
}

}

答案 9 :(得分:0)

问题是您需要找出导致崩溃的线路。

由于画布没有显示详细错误,使用 OSLog 和 Console.app 进行调试将是一个可能的解决方案。

例如:

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)
    }
}

记得使用过滤器来更好地捕捉来自控制台的调试消息。

在找出导致崩溃的线路后,您可以进一步查看线路并继续该过程,直到找到罪魁祸首。

(就我而言,我忘记将 UUID 添加到导致画布崩溃的预览数据中。)