如何将核心数据模型项传递到视图中进行编辑

2024-01-24

我有一个最小的示例项目CD传递Q https://github.com/ericg-xcode-questions/CDPassingQ

我的主要(内容视图 https://github.com/ericg-xcode-questions/CDPassingQ/blob/main/CDPassingQ/ContentView.swift) 好像:

import SwiftUI
import CoreData

struct ContentView: View {
    @Environment( \.managedObjectContext ) private var viewContext
    
    @FetchRequest( sortDescriptors: [ NSSortDescriptor( keyPath: \Item.name, ascending: true ) ],
                   animation:       .default )
    private var items: FetchedResults<Item>
    
    var body: some View {
        NavigationView {
            List {
                ForEach( items ) { item in
                    NavigationLink {
                        NameViewer( itemID: item.objectID )
                    } label: {
                        Text( item.name! )
                    }
                }
                .onDelete( perform: deleteItems )
            }
            .toolbar {
                ToolbarItem( placement: .navigationBarTrailing ) {
                    EditButton()
                }
                
                ToolbarItem {
                    Button() {
                        print( "Add Item" )
                    } label: {
                        NavigationLink {
                            NameViewer();
                        } label: {
                            Label( "Add Item", systemImage: "plus" )
                        }
                    }
                }
            }
        }
    }
    
    
    
    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            offsets.map { items[$0] }.forEach(viewContext.delete)
            
            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)")
            }
        }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

and 名称查看器 https://github.com/ericg-xcode-questions/CDPassingQ/blob/main/CDPassingQ/NameViewer.swift好像:

import SwiftUI
import CoreData

enum TrustReason: String, Identifiable, CaseIterable
{
    var id: UUID
    {
        return UUID();
    }
    
    case unknown         = "Unknown";
    case legalOnly       = "Legal Only";
    case goodLabeling    = "Good Labeling";
    case facilityClean   = "Facility Clean";
    case detailedAnswers = "Detailed Answers";
    case unresponsive    = "Unresponsive";
}



extension TrustReason
{
    var title: String
    {
        switch self
        {
            case .unknown:
                return "Unknown";
                
            case .legalOnly:
                return "Legal Only";
                
            case .goodLabeling:
                return "Good Labeling";
                
            case .facilityClean:
                return "Facility Clean";
                
            case .detailedAnswers:
                return "Detailed Answers";
                
            case .unresponsive:
                return "Unresponsive";
        }
    }
}



struct NameViewer: View {
    @Environment( \.presentationMode )     var         presentationMode
    @Environment( \.managedObjectContext ) private var moc
    
    @State private var name: String = ""
    @State private var reason: TrustReason = .unknown

    var itemID: NSManagedObjectID?
    
    var body: some View {
        Form {
            Section( header: Text( "Information" ) ) {
                TextField( "Name", text: $name )
            }
            
            Section( header: Text( "Trust" ) ) {
                Picker( "Reason", selection: $reason ) {
                    ForEach( TrustReason.allCases ) { trustReason in
                        Text( trustReason.title ).tag( trustReason )
                    }
                }
            }
        }
        .toolbar {
            Button() {
                if ( saveName() ) {
                    self.presentationMode.wrappedValue.dismiss()
                }
            } label: {
                Text( "Save" )
            }
        }
        .onAppear {
            print( "on appear" )
            
            guard let theID = itemID,
                  let item = moc.object( with: theID ) as? Item else {
                      return
                  }
            
            print( "passed guard" )
            
            if let itemName = item.name {
                name = itemName
            }
            
            print( name )
        }
    }
    
    
    
    private func saveName() -> Bool {
        let item = Item( context: moc )
        
        do {
            print( self.name )
            
            item.name = self.name
            
            try moc.save()
            
            return true
        } catch {
            print( error )
            print( error.localizedDescription )
        }
        
        self.moc.rollback();
        
        return false
    }
}



struct NameViewer_Previews: PreviewProvider {
    static var previews: some View {
        NameViewer()
    }
}

我可以创建要显示在 ContentView 列表中的新项目。

然后,当我选择列表中的某个项目时,我将该项目传递给 NameViewer。我可以确认我在 .onAppear 代码中成功找到了正确的对象。

然而,存在两个问题:

  1. 如果我在列表中选择一个项目,则项目名称不会出现在名称文本字段中,除非我先单击文本字段。

  2. 使用 .onAppear 似乎不是放置该代码的正确位置。原因是选取器将另一个视图推送到堆栈上,一旦选取该项目,.onAppear 会再次运行,并且我会丢失对名称字段的更改名称。

我该如何更改代码来解决这些问题?


为了实现所需的功能,我将更改 UI 和核心数据方面的架构。

在用户界面方面,最好使用导航链接来显示静态数据详细视图,并使用模态来执行数据操作,例如创建和编辑对象。因此,有一个视图来显示对象详细信息(例如NameViewer)和另一个编辑对象(例如NameEditor)。另外,绑定你的属性NSManagedObject直接子类化 SwiftUI 控件。不要创建额外的@State属性,然后复制值。您引入了共享状态,而 SwiftUI 正是为了消除这种共享状态。

在核心数据方面,为了执行创建和更新操作,您需要使用子上下文。每当您创建或更新对象时,都会显示一个注入了子上下文的模式编辑器视图。这样,如果我们对更改不满意,我们可以简单地忽略模态和更改会被神奇地丢弃,而无需调用rollback(),因为子上下文会被视图破坏。由于您现在正在使用子上下文,因此也不要忘记将主视图上下文保存在某个地方,例如当用户导航出您的应用程序时。

因此,为了在代码中实现这一点,我们需要一些结构来存储新创建的对象以及它们的子上下文:

struct CreateOperation<Object: NSManagedObject>: Identifiable {
    let id = UUID()
    let childContext: NSManagedObjectContext
    let childObject: Object
    
    init(with parentContext: NSManagedObjectContext) {
        let childContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
        childContext.parent = parentContext
        let childObject = Object(context: childContext)
        
        self.childContext = childContext
        self.childObject = childObject
    }
}

struct UpdateOperation<Object: NSManagedObject>: Identifiable {
    let id = UUID()
    let childContext: NSManagedObjectContext
    let childObject: Object
    
    init?(
        withExistingObject object: Object,
        in parentContext: NSManagedObjectContext
    ) {
        let childContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
        childContext.parent = parentContext
        guard let childObject = try? childContext.existingObject(with: object.objectID) as? Object else { return nil }
        
        self.childContext = childContext
        self.childObject = childObject
    }
}

UI代码如下:

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.name, ascending: true)], animation: .default
    ) private var items: FetchedResults<Item>
    @State private var itemCreateOperation: CreateOperation<Item>?
    
    var body: some View {
        NavigationView {
            List {
                ForEach(items) { item in
                    NavigationLink {
                        NameViewer(item: item)
                    } label: {
                        Text(item.name ?? "")
                    }
                }
            }
            .toolbar {
                ToolbarItemGroup(placement: .navigationBarTrailing) {
                    EditButton()
                    Button(action: {
                        itemCreateOperation = CreateOperation(with: viewContext)
                    }) {
                        Label("Add Item", systemImage: "plus")
                    }
                }
            }
            .sheet(item: $itemCreateOperation) { createOperation in
                NavigationView {
                    NameEditor(item: createOperation.childObject)
                        .navigationTitle("New Item")
                }
                .environment(\.managedObjectContext, createOperation.childContext)
            }
        }
    }
}

struct NameViewer: View {
    @Environment(\.managedObjectContext) private var viewContext
    @State private var itemUpdateOperation: UpdateOperation<Item>?
    
    @ObservedObject var item: Item
    
    var body: some View {
        Form {
            Section {
                Text(item.name ?? "")
            }
        }
        .navigationTitle("Item")
        .toolbar  {
            Button("Update") {
                itemUpdateOperation = UpdateOperation(withExistingObject: item, in: viewContext)
            }
        }
        .sheet(item: $itemUpdateOperation) { updateOperation in
            NavigationView {
                NameEditor(item: updateOperation.childObject)
                    .navigationTitle("Update Item")
            }
            .environment(\.managedObjectContext, updateOperation.childContext)
        }
    }
}

struct NameEditor: View {
    @Environment(\.dismiss) private var dismiss
    @Environment(\.managedObjectContext) private var childContext
    
    @ObservedObject var item: Item
    
    var body: some View {
        Form {
            Section(header: Text("Information")) {
                if let name = Binding($item.name) {
                    TextField("Name", text: name)
                }
            }
        }
        .toolbar {
            Button() {
                try? childContext.save()
                dismiss()
            } label: {
                Text("Save")
            }
        }
    }
}

更多信息请参见我的相关回答:

  • 如何在 SwiftUI 中实现子上下文 (CoreData) 环境? https://stackoverflow.com/a/71032785/7337547
  • SwiftUI - 将 @Binding 与核心数据结合使用 NS托管对象? https://stackoverflow.com/questions/57614564/swiftui-use-binding-with-core-data-nsmanagedobject/67174011#67174011
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何将核心数据模型项传递到视图中进行编辑 的相关文章

随机推荐

  • 使用带有新值的片段多次并进行后台维护

    我正在制作一个应用程序滑动菜单 and tab在底部 整个应用程序是fragment基于 我面临一些我正在使用的问题片段A然后点击一个按钮并转到片段B in 片段B有一个按钮 点击这个按钮片段A将用新值更新 等等 意味着应该发生从 A gt
  • 如何在 C# 中应用 Word 快速样式 - 不仅仅是简单的格式设置而是整个样式?

    我承认 我对使用 Interop 库很陌生 但人们似乎总是给出的建议是 记录一个宏并检查 vba 代码 问题是 宏没有准确记录我在做什么 单击快速样式将其应用到当前选择 我的任务非常简单 我需要将快速样式应用于段落 Microsoft Of
  • 在已经运行 64 位 Oracle 数据库服务器的 Windows Server 上安装 Oracle 32 位客户端

    我在Windows 2008 R2上安装了64位Oracle数据库服务器 11 2 0 3 自然地 它会自动安装64位客户端 我必须在该 32 位服务器上安装一个应用程序 并且需要 32 位 Oracle 客户端 不要问 我无法安装此应用程
  • 在R中的轴标题中同时使用下标和变量值

    我想在 R 的绘图中使用标题 湿地中的二氧化碳排放量 而 CO2 中的 2 位于下标中 并且该区域的值 此处 湿地 包含在名为 区域 的变量中 region wetlands plot 1 1 main expression CO 2 pa
  • 删除 Parse 上的多条记录

    我的类在 Parse 上有超过 1 1 亿条记录 并且希望根据一条规则删除 99 的记录 状态 1 我设置了一些后台作业来逐个记录此记录 但速度太慢并且类慢慢变大 有什么办法可以更快地删除它们吗 也许在设定日期之前删除所有对象 Thanks
  • 在 Intellij Idea Community Addition 中安装 PHP 插件

    我正在尝试在我的 Intellij Idea IDE 社区版版本 14 中安装 PHP 插件 我相信这是非常受支持的 因为我看到有一个插件网站显示社区 PHP 插件 URL https plugins jetbrains com plugi
  • 在 Javascript 中覆盖警报并确认

    有没有办法覆盖alert and confirm 在 JavaScript 中 我使用 jQuery 所以如果有办法在这个框架中做到这一点 我将不胜感激 我想做这样的事情 override alert msg Show custom stu
  • 按数组列表自定义分页

    我想在java代码中通过数组列表创建自定义分页 import org springframework data domain Sort import org springframework data domain PageRequest i
  • 从字典列表中创建不矛盾项的字典

    这个问题的灵感来自于这个问题 https stackoverflow com q 9906944 589206 我想从字典列表中获取一个字典 该字典应包含所有字典中的所有键 值对 这些字典要么只包含一次 要么所有字典都同意关联值 示例 取自
  • (在Eclipse中使用处理库)如何使用窗口模式?

    http processing org learning eclipse http processing org learning eclipse 根据步骤5 我使用了PApplet main new String present MyGa
  • 检索数据时数据库崩溃问题

    This is the Database field values 下面是我用来从 sqlite 数据库获取数据的代码 void readDataFromRestaurantTable self openDataBase const cha
  • 如何退出gdb中的wh模式

    gdb 中的 wh 模式对我来说效果不太好 当我转储一些数据时 命令窗口和程序文本窗口会重叠 看起来很混乱 我想退出 wh 模式而不退出调试器 也不终止正在运行的程序 我设置了几个断点 所以我不想退出调试器 C x C a C x a C
  • 无法让 CSS 属性选择器工作

    我正在尝试将此 css 转换为使用属性 starts with 选择器 因为我有几个带有 id 属性的锚元素 它们以相同的值开始 a cta button 127944 79d30f48 4e68 43c8 949d a9734a713b3
  • 为什么我应该使用bundle exec 而不是仅仅运行命令? [复制]

    这个问题在这里已经有答案了 使用运行可执行文件是众所周知的最佳实践bundle exec
  • 每 4 件商品替换颜色 [重复]

    这个问题在这里已经有答案了 是否可以使用纯 CSS 创建一个每 4 个项目交替的颜色网格 例如 前 4 个项目为蓝色 接下来的 4 个为红色 然后接下来的 4 个项目为蓝色 依此类推 div Item 1 blue div div Item
  • T-SQL 将多行连接并分组为一行[重复]

    这个问题在这里已经有答案了 我正在寻找一种分组方法 同时还将行连接成逗号分隔的字符串 例子 Name Place Steve Jones New York Steve Jones Washington Albert Smith Miami
  • 将节点从 6 升级到最新版本时,npm i 在 React Native 项目中失败并显示 ENOENT

    在 React Native 项目上将 npm 从 6 14 升级到最新版本时 使用节点14 nvm切换节点版本 理想情况下我想转到节点16 但是特别是在更新npm时 无论14还是16都会发生类似的问题 我也在 mac M1 上使用自制程序
  • 如何计算交易的最低硬币找零?

    嘿大家 我有个问题 我正在使用 Visual Basic Express 我应该计算交易的变化 现在我会使用什么代码 我已经部分工作了 但它开始变得有点混乱 谢谢 对于那些想了解更多信息的人 假设我有一美元 我去商店买东西 我必须要求用户输
  • 无法禁用 OpenCart (PHP) 中的错误报告

    我似乎无法禁用 PHP 中的错误报告 我已经尝试了所有方法 但仍然显示 注意 错误 我的 php ini 有 display errors Off error reporting 0 我的 htaccess 有 php value erro
  • 如何将核心数据模型项传递到视图中进行编辑

    我有一个最小的示例项目CD传递Q https github com ericg xcode questions CDPassingQ 我的主要 内容视图 https github com ericg xcode questions CDPa