Swift 和 SwiftUI 之间的 viewContext 是不是不同

Posted

技术标签:

【中文标题】Swift 和 SwiftUI 之间的 viewContext 是不是不同【英文标题】:Is viewContext different between Swift and SwiftUISwift 和 SwiftUI 之间的 viewContext 是否不同 【发布时间】:2021-01-24 01:41:01 【问题描述】:

编辑:我在底部添加了我的问题的改写。

所以我有一个我已经开发了很长时间的应用程序。我用它来自学 Xcode / Swift,现在我又拥有一台 Mac,我正在尝试通过将应用程序完全重新构建为一个新项目来学习 SwiftUI。

我在 CoreData 中有一个实体,它只包含 1 条记录,它具有我正在跟踪的设置的所有属性。

之前,我可以对我的实体进行扩展,Ent_Settings,这样我就可以始终取回该特定记录。

class var returnSettings: Ent_Settings 
    //this is inside of extension Ent_Settings
    
    let moc = PersistenceController.shared.container.viewContext
    var settings: Ent_Settings?
    let fetchRequest: NSFetchRequest<Ent_Settings> = Ent_Settings.fetchRequest()
    
    do 
        let results = try moc.fetch(fetchRequest)
        
        if results.count == 0 
            Ent_Settings.createSettingsEntity()
            
            do 
                let results = try moc.fetch(fetchRequest)
                settings = results.first!
             catch 
                error.tryError(tryMessage: "Failed performing a fetch to get the Settings object after it was created.", loc: "Ent_Settings Extension")
            
         else 
            settings = results.first!
        
     catch 
        error.tryError(tryMessage: "Fetching Settings Object", loc: "Ent_Settings Extension")
    
    
    return settings!

由于没有AppDelegate,我花了一段时间才弄清楚如何获取 moc,但您可以看到我在代码中找到了并且看起来效果很好。

但是,当我转到我在 SwiftUI 中创建的视图时,我得到了奇怪的结果。 在模拟器中,我可以运行我的应用程序并查看更改。离开视图并返回并查看更改已保存。但是,如果我重建并运行应用程序,它会丢失所有数据。所以它似乎根本不记得事情。基本上,它是易失性内存而不是持久性内存。但是如果我在Ent_Settings 的扩展中添加另一个函数来更新该属性,然后再次构建并运行,它将永久保存。

我想知道它是否与上下文的管理方式有关,也许它没有看到变化,因为它是一个不同的“侦听器”?

这是我在视图中所做的更改。 (我试着去掉那些让它更难看到的多余的东西)

import SwiftUI

struct Settings_CheckedView: View 
    
    @Environment(\.managedObjectContext) private var viewContext
    @State private var picker1 = 0
    
    var body: some View 
        Form 
            Section(header: Text("..."),
                    footer: Text("...")) 
                VStack 
                    HStack  ... 
                    Picker("", selection: $picker1) 
                        Text("Off").tag(0)
                        Text("Immediately").tag(1)
                        Text("Delay").tag(2)
                    
                    .pickerStyle(SegmentedPickerStyle())
                    .onChange(of: picker1, perform:  value in
                        
                        settings.autoDeleteSegment = Int16(value)
                        
                        viewContext.trySave("Updating auto-delete segment value in Settings", 
                                            loc: "Ent_Settings Extension")
                    )
                
            
        ...
        ...
        .onAppear(perform: 
            settings = Ent_Settings.returnSettings
            
            self.picker1 = Int(settings.autoDeleteSegment)
            self.picker2 = Int(settings.deleteDelaySegment)
        )
    

这是我用于trySave() 函数的代码,我在viewContext 上拥有

extension NSManagedObjectContext 
    
    func trySave(_ tryMessage: String, loc: String) 
        
        let context = self
        if context.hasChanges 
            do 
                try self.save()
             catch 
                print("Attempted: \(tryMessage) -in \(loc)")
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            
        
    

此代码似乎实际上并未将任何内容保存到数据库中。

我还尝试将此代码添加到 Settings_CheckedView 中,看看是否会产生影响,但我得到了奇怪的结果。

@FetchRequest(sortDescriptors:[])
private var settingsResult: FetchedResults<Ent_Settings>

但即使我知道 Ent_Settings 有 1 条记录,它也会返回零结果。所以这让我想知道我是否真的按照我的方式创建了两个数据库。

我对 SwiftUI 真的很陌生,CoreData 似乎是那些很难获得信息的东西之一。希望有人能指出我哪里出错了。我希望能够在 Swift 和 SwiftUI 中对 CoreData 进行更改。

感谢您提供的任何帮助,如果您需要添加任何其他内容,请告诉我。

已编辑:尝试改写问题

所以最终,我想在该实体的扩展中使用 CoreData 做一些事情。我的数据库有很多方面,但我还在其中创建了一个实体来存储设置信息,并且由于该实体与其他任何内容都不相关,我认为这将是学习 SwiftUI 的一个很好的起点。我可以在 UIKit 中使用所有东西,但我也特别想学习 SwiftUI。

根据我对 SwiftUI 的理解,它旨在替换故事板并使生命周期变得更加容易。所以我处理 CoreData 的类仍然应该在 SwiftUI 外部执行。

正如您在上面的Settings_CheckedView 视图中所见,我从Ent_Settings 的扩展中引用了该单个设置记录。基本上,该扩展程序中的所有内容都负责返回设置的单个记录,检查该记录是否存在,如果不存在则创建它(基本上是第一次运行应用程序)

理想情况下,我希望将功能保留在 Ent_Settings 的扩展中,但我的问题是我无法获得正确的 moc 实例来持久保存它。

@main
struct MyApp: App 
    let persistenceController = PersistenceController.shared

    var body: some Scene 
        WindowGroup 
            MainMenuView().onAppear(perform: 
                if Ent_Lists.hasAnyList(persistenceController.container.viewContext) == false 
                    Ent_Lists.createAnyList(persistenceController.container.viewContext)
                
                
                if Ent_Section.hasBlankSection(persistenceController.container.viewContext) == false 
                    Ent_Section.createBlankSection(persistenceController.container.viewContext)
                
                
                //self.settings = Ent_Settings.returnSettings(persistenceController.container.viewContext)
            )
            //ContentView()
                //.environment(\.managedObjectContext, persistenceController.container.viewContext)
        
    


我很确定let persistenceController = PersistenceController.shared 正在初始化整个应用程序中使用的持久控制器。创建或检索它。

所以,我首先在 Ent_Settings 中尝试了这个: let moc = PersistenceController.shared.container.viewContext 但我认为这可能是在 MyApp 内部创建的 PersistenceController 之外创建一个新的 PersistenceController

我也尝试了let moc = EZ_Lists_SwiftUI.PersistenceController.shared.container.viewContext,但我很确定这也会创建一个新实例,因为我只能访问大写的 PersistenceController 而不是小写的。

我也尝试像这样从视图中传递 moc:class func createSettingsEntity(_ moc: NSManagedObjectContext) 但我收到关于 moc 为 nil 的错误,所以我猜 moc 不能通过引用从 Struct 发送。

线程 1:“+entityForName: nil 不是用于搜索实体名称 'Ent_Settings' 的合法 NSPersistentStoreCoordinator”

为了清楚起见,我将其添加到视图中:@Environment(\.managedObjectContext) private var viewContext 将此 @State 属性添加到视图中:@State private var settings: Ent_Settings! 并将其设置在 .onAppear 中,其中:self.settings = Ent_Settings.returnSettings(self.viewContext)

所以我真正在寻找的是如何从Ent_Settings 的扩展中访问 moc。在我纯粹是 UIKit 和 Storyboards 的应用程序中,我使用了这个:let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext,但由于不再有 AppDelegate,我不能再使用它了。当您在使用 SwiftUI 但在视图之外工作时(例如在课堂上或像我一样的东西中),用什么代替它来获得 moc?谢谢你。希望这些额外信息有助于解释我的问题。

【问题讨论】:

【参考方案1】:

如果您只使用 coredata 来存储设置,我会查看 UserDefaults 以及是否符合您的用例。 https://developer.apple.com/documentation/foundation/userdefaults 如果您出于某种原因需要使用 CoreData,我建议您启用 CloudKit,因为 CloudKit 将允许您通过 Web 控制台实际查看保存的数据。

此外,SwiftUI 不会为您创建 AppDelegate,您仍然可以像使用 UIKit 一样创建自己的 AppDelegate.swift 文件。 只需在结构声明之前将@UIApplicationDelegateAdaptor 定义到您最顶层结构(带有@main 的那个)中的自定义AppDelegate.swift 文件中。

  @main
    struct MainAppView: App 
        @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

【讨论】:

感谢您的推荐。实际上,我在那里有很多东西作为相关数据库,只是想轻松开始。不过,我会尝试按照您推荐的方式访问 appDelegate,谢谢。 我正在进一步阅读您的帖子,看起来您正在给我代码以在 SwiftUI 中创建 AppDelegate。但我实际上想要相反的。我正在查看您如何使用@Environment 在 SwiftUI 中获取 moc,但是如果我正在创建一个也需要 moc 的类,那么我该如何获取它,因为它在 SwiftUI 之外。这就是我提到 AppDelegate 的原因,因为在 SwiftUI 出现之前我就是这样获得它的。 我更新了我的问题,希望它能让我更清楚我在寻找什么。【参考方案2】:

好吧,我脸上有点鸡蛋。我终于明白了为什么我会遇到这么多麻烦。当我在做我的应用程序时,我把所有与苹果提供的 ContextView 相关的东西都注释掉了,注释掉的太多了。

//ContentView()
    //.environment(\.managedObjectContext, persistenceController.container.viewContext)

我丢失了设置环境对象的部分。把它放回去后,我现在可以保存到 moc 中了。

在我的实体类扩展中,我以这种方式访问​​ moc:

let moc = MyApp.PersistenceController.shared.container.viewContext

感谢所有查看并尝试提供帮助的人。原来我是在开枪打自己的脚。

【讨论】:

以上是关于Swift 和 SwiftUI 之间的 viewContext 是不是不同的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SwiftUI 中的类和视图之间共享数据

iOS 15 中的 Swift 和 SwiftUI

Swift之深入解析如何结合Core Data和SwiftUI

在 2 个页面 SwiftUI 之间传递状态

Swift之深入解析SwiftUI布局如何自定义AlignmentGuides

SwiftUI:如何使用延迟在两个图像之间设置动画过渡?