Core data SwiftUI预览画布和核心数据

Core data SwiftUI预览画布和核心数据,core-data,swiftui,Core Data,Swiftui,预览画布正在崩溃,但在模拟器中一切正常。我假设它与@ObservedObject和@Fetchrequest有关 这里的尝试解决方案 不起作用 import SwiftUI import CoreData struct TemplateEditor: View { @Environment(\.managedObjectContext) var managedObjectContext @FetchRequest( entity: GlobalPlaceholders.enti

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

这里的尝试解决方案

不起作用

 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)

    }
}

导入快捷界面
导入CoreData
结构模板编辑器:视图{
@环境(\.managedObjectContext)变量managedObjectContext
@获取请求(
实体:GlobalPlaceholders.entity(),
sortDescriptors:[
NSSortDescriptor(键路径:\GlobalPlaceholders.category,升序:false),
]
)变量占位符:FetchedResults
@ObservedObject变量文档模板:模板
@国家私有变量documentTemplateDraft=documentTemplateDraft()
@绑定变量编辑:Bool
var body:一些观点{
VStack(){
HStack(){
取消按钮
垫片()
保存按钮
}.padding()
添加按钮
ForEach(占位符){中的占位符
文本(占位符.名称)
}
TextField(“Title”,text:$documentTemplateDraft.Title)
TextField(“Body”,text:$documentTemplateDraft.Body)
.padding()
.框架(宽:100,高:400)
垫片()
}
...
}
结构模板编辑器\u预览:PreviewProvider{
静态var预览:一些视图{
让managedObjectContext=(UIApplication.shared.delegate为!AppDelegate)。persistentContainer.viewContext
let request=NSFetchRequest(entityName:“模板”)
request.sortDescriptors=[NSSortDescriptor(keyPath:\Templates.created,升序:false)]
让documentTemplate=try!managedObjectContext.fetch(请求)。首先作为!模板
返回TemplateEditor(documentTemplate:documentTemplate,编辑:。常量(true)).environment(\.managedObjectContext,managedObjectContext)。environmentObject(documentTemplate)
}
}

预计将生成预览

一个选项是不在预览中使用核心数据。这足以帮助查看我正在构建的UI,但我仍需要使用模拟器来测试功能

#if !DEBUG
// Core Data related code e.g. @FetchRequest
#endif
Xcode 11.0版(11A419c)Mac OS 10.15 Beta版(19A558d)中的建议对我有效。我的崩溃日志显示索引错误

***由于未捕获异常“NSRangeException”而终止应用程序,原因:“***-[\uu NSArray0 objectAtIndex:]:索引0超出空NSArray的界限”


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

所以,如果你在预览中的一个appear处理程序中放入一些代码,它将在启动时运行。它甚至会在你键入时实时更新

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()
    }
  }
}
请注意,我已经在
AppDelegate
上用静态方法将我的
viewContext
包装起来,以使访问稍微不冗长,更容易记住:

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

  static var viewContext: NSManagedObjectContext {
    return persistentContainer.viewContext
  }

如果没有数据,我不确定您的试用线路是否有效

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

我还注意到我需要.environment(.managedObjectContext,context)托管CoreData视图或预览的早期选项卡视图中的代码将失败。

这个答案在我最近的项目中似乎有效,它替换了默认的ContentView\u Previews结构,尽管其他人质疑它是否会提取持久数据。这要归功于Xcode Beta 7中主/详细模板项目中的@ShadowDES-

我可以使用Canvas(XCode版本11.3(11C29))对任何东西进行CRUD,而且它似乎运行得完美无缺

    #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


适用于使用
应用程序
模板的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
    }()
}
不清楚这有什么帮助,但现在它工作得很好。此外,您可以将示例数据添加到我用注释标记的
上下文中。

这是我的解决方案

我不想在视图中使用CoreData。我想要MVVM样式。 所以您需要模拟核心数据,以便在画布视图中显示

这是一个例子:

// 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)
对我有用的是:

我在persistence controller的preview属性中创建了我的所有示例数据,在启动项目时使用以下设置生成Xcode模板:Interface-SwiftUI、Lifecycle-SwiftUI App、Use Core data、Host in 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)")
            }
        })
    }
}

它崩溃是因为在
PersistenceController
中指示它这样做:

struct PersistenceController{
...
静态变量预览:PersistenceController={
...
做{
请尝试viewContext.save()
}抓住{
//将此实现替换为适当处理错误的代码。
//fatalError()导致应用程序生成崩溃日志并终止。您不应在装运应用程序中使用此函数,尽管它在开发过程中可能很有用。
设nsError=错误为nsError
fatalError(“未解决的错误\(nsError),\(nsError.userInfo)”)
}
返回结果
}()
...
}
因此,实际原因可以在崩溃报告中看到。实际上,XCode 12.4显示了关于检查崩溃报告的警告;但是,对于像我这样的web开发新手来说,报告过于冗长。因此,我花了一段时间才发现问题,所以我希望这能为其他人节省一些时间


…在我的例子中,问题是在为预览填充核心数据模型时未设置必需的属性。

这对我很有效。在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)
}

}

问题是,您需要找出是哪条线路导致了崩溃

因为画布没有显示详细的错误,所以使用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)
    }
}
记住使用过滤器更好地捕获来自的调试消息
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)
    }
}