如何使用 SwiftUI 在模态视图中创建 NSManagedObject?

Posted

技术标签:

【中文标题】如何使用 SwiftUI 在模态视图中创建 NSManagedObject?【英文标题】:How to create a NSManagedObject in a modal view using SwiftUI? 【发布时间】:2020-09-21 20:42:12 【问题描述】:

如何使用 SwiftUI 在模态视图中创建新的 ManagedObject (MO)?

遇到一个奇怪的错误,Xcode 消耗 GB 内存并通过交换文件填满 Mac 上的硬盘。

当在.sheet 修饰符中创建模态视图时,似乎会创建某种无限循环,该循环会用注入该模态视图的 ManagedObject 的副本填充内存。

此示例项目至少说明了部分问题。如果你运行它,你会看到 .sheet 修饰符中调用的方法一遍又一遍地触发。一种理论是,下方显示 ManagedObjects 列表的屏幕会导致两个屏幕之间出现某种循环。

https://github.com/sphericalwave/ChildContextTest

希望在模态屏幕中使用 childContext,因此如果模态视图在未保存 childContext 的情况下被关闭,则任何未保存的更改都将被丢弃。但首先需要清除这个障碍,跨上下文共享 ManagedObject 存在一些挑战。

import CoreData
import SwiftUI

struct CrtFdsUI: View

    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: CrtFd.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CrtFd.scale, ascending: true)])
    var crtFds: FetchedResults<CrtFd>
    @State var showModal = false
    @ObservedObject var absFd: AbsFd
    
    func crtFdModal() -> CrtFdUI 
        print("func crtFdModal() -> CrtFdUI")
        let cF = CrtFd(scale: 1.0, absFd: absFd, moc: moc)
        return CrtFdUI(crtFd: cF)
    
    
    var body: some View 
        NavigationView 
            VStack 
                List 
                    ForEach(self.crtFds, id: \.objectID) 
                        CrtFdCell(crtFd: $0)
                    
                
                .navigationBarTitle("CrtFdsUI")
                .navigationBarItems(trailing: PlusBtn(showModal: $showModal))
            
            .sheet(isPresented: $showModal)  self.crtFdModal()   //FIXME: Called in endless loop?
        
    

这是 managedObjects 的列表。

import CoreData
import SwiftUI

struct CrtFdsUI: View

    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: CrtFd.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \CrtFd.scale, ascending: true)])
    var crtFds: FetchedResults<CrtFd>
    @State var showModal = false
    @ObservedObject var absFd: AbsFd
    
    func crtFdModal() -> CrtFdUI 
        print("func crtFdModal() -> CrtFdUI")
        let cF = CrtFd(scale: 1.0, absFd: absFd, moc: moc)
        return CrtFdUI(crtFd: cF)
    
    
    var body: some View 
        NavigationView 
            VStack 
                List 
                    ForEach(self.crtFds, id: \.objectID) 
                        CrtFdCell(crtFd: $0)
                    
                
                .navigationBarTitle("CrtFdsUI")
                .navigationBarItems(trailing: PlusBtn(showModal: $showModal))
            
            .sheet(isPresented: $showModal)  self.crtFdModal() 
        
    

【问题讨论】:

【参考方案1】:

现在,每次重新计算视图层次结构时,代码都会创建一个 CrtFd 的新实例。这可能不是一个好主意,因为由于您无法直接控制的原因,可能会意外重新计算层次结构,因此即使没有无限循环的创建,您最终可能仍会得到比您想要的更多的新托管对象。

我下载了你的项目,但我不确定这两个 Core Data 实体代表什么,但我确实注意到,当 CrtFdsUI 第一次出现时,它的 absFd 已经具有 crtFd 属性的值,并且crtFd 属性是一对一而不是一对多。这意味着,当您在此代码中创建一个新实例时,您会将一个 CrtFd 替换为另一个完全相同的实例。

我猜你并不想用相同的副本替换一个实例,所以避免这个问题的一种方法是更改​​你的 crtFdModal() 以使用已经存在的实例,并且仅在还没有副本的情况下创建一个新副本:

func crtFdModal() -> CrtFdUI 
    print("func crtFdModal() -> CrtFdUI \(showModal)")
    let cF = absFd.crtFd ?? CrtFd(scale: 1.0, absFd: absFd, moc: moc)
    return CrtFdUI(crtFd: cF)

这避免了您描述的问题。很难判断它是否正是您所需要的,但由于您的代码创建了明显不必要的重复项,因此很可能确实如此。

【讨论】:

对不起,令人困惑。这是一个示例项目,因为我真的不知道问题是什么。将 CrtFd 视为您吃的食物(100 克萝卜)的一个实例。 AbsFd 是食物的概念(1 x 100 克萝卜有一堆相关的大量营养素,图片等)。因此,当您创建 CrtFd 时,您的想法是记录您吃过的食物的实例。 所以 AbsFd 可以知道许多 CrtFd(我没有在 Core Data 模型中正确建立这种关系,但我只是在 GitHub 上更新了它)。 上面的解决方案不是我想要的,因为当您按下 + 按钮时,您正在记录 Turnip 的新实例。所以我确实想创建一个新的 CrtFd 并将其与 AbstractFood 相关联。我只是对要使用的 absFd 进行硬编码,因为这样我可以简化示例项目。它的连接方式没有多大意义,但它展示了 ViewHierarchy 重新计算并导致循环的挑战。 我明白了。如果您的问题包含这些关键细节,将会有所帮助。

以上是关于如何使用 SwiftUI 在模态视图中创建 NSManagedObject?的主要内容,如果未能解决你的问题,请参考以下文章

如何检测模态视图在 SwiftUI 中全局可见

如何在 SwiftUI 中关闭 ResearchKit 模态视图?

如何在 SwiftUI 中制作具有透明背景的模态视图?

SwiftUI - 导航栏未显示在模态导航视图中

SwiftUI - 如何关闭假的模态视图 - 从里面的第二个视图,用关闭按钮?

如何在 SwiftUI 中动态推送视图或呈现视图?