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

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(使用前将#替换为@)

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

  • Expo eas-cli iOS 构建失败

    我已经使用 React Native 创建了一个应用程序 并尝试通过 Expo 的 eas cli 创建一个 iOS 应用程序商店 跑步时eas build platform ios the Fastlane build failed wi
  • Swift 中 UIViewController 子类成员的双重初始化

    我想制作一个自定义容器视图控制器并向其子类添加一些成员UIViewController 当我尝试使用以下代码从应用程序委托初始化它时 self window UIWindow frame UIScreen mainScreen bounds
  • 如何创建一个仅接收一次信号,然后自行取消订阅/释放的 ReactiveCocoa 订阅者?

    我目前正在注册一个属性信号的订阅者 如下所示 RACAble self test subscribeNext id x NSLog signal fired 默认功能是每次 self test 更改时都会触发 但我只想触发一次 然后取消订阅
  • 应用因广告标识符 (IDFA) 被拒绝

    我的申请因以下原因被拒绝 您和您的应用程序 以及与您有联系的任何第三方 签订广告服务合同 可以使用广告标识符 以及通过使用广告获得的任何信息 标识符 仅用于服务广告的目的 如果一个用户 重置广告标识符 则您同意不合并 直接或间接关联 链接或
  • iOS 应用内购买沙箱测试显示我的密码错误

    我在 itunesconnect 中创建了一个 Sandbox Tester 帐户 并通过我在网络上注册的电子邮件验证了电子邮件地址 验证后 我尝试使用沙盒帐户在我的应用程序中购买商品 并收到更改密码的提醒 所以我做了 然后我尝试使用新密码
  • “无法调用非函数类型‘HTTPURLResponse’的值?”(Alamofire 4.0) [Swift 3.0]

    我收到此错误 无法调用非函数类型 HTTPURLResponse 的值 关于该部分 response request response data error 我想知道是否有人可以帮助我解决这个问题 Alamofire download ur
  • 如何创建一个 NSMutableArray 并为其分配一个特定的对象?

    我刚刚开始接触 Obj C 并且希望创建一个 MKAnnotations 数组 我已经创建了名为的 MKAnnotation 类TruckLocation其中包含名称 描述 纬度和经度 这是到目前为止我所拥有的数组 NSMutableArr
  • 从后台唤醒时应用程序会重新启动

    iOS 大师您好 我已经广泛搜索了答案 但找不到答案 我打赌对我的问题的第一个答复将是另一个类似的问题 但我找不到它 不管怎样 我的问题是我正在运行一个简单的地图应用程序 用户可以在地图上放置图钉 并在放置的图钉周围有一个自定义的圆圈覆盖
  • SwiftUI NavigationView 看不到图像

    我有一个代码并制作 NavigationLink 按钮 我编写文本和图像 但我的图像看不到 请帮助我 VStack Image Coachs resizable aspectRatio contentMode fill frame widt
  • 根据图像制作具有 UIImageView 高度的 UICollectionViewCells

    我想做一个UICollectionView单元格的宽度是屏幕的宽度 但高度取决于单元格的宽高比UIImageView inside 在我目前的实施中 所有UICollectionViewCell实例是正方形 这不是我想要的 我想以某种方式使
  • iPhone - 创建图像网格列表

    我正在开发一个应用程序 它从网站获取图像并使用网格视图向用户显示 例如Apple的照片应用程序 当您点击图像时 我会推送一个显示图像信息和其他内容的视图控制器 实现这个的更好方法是什么 我想到了自定义 UITableViewCell 我见过
  • prepareForSegue 和代表

    我有一个有两个 segue 的应用程序 在其中一个 Segue 中 当前视图控制器成为委托 而另一个则不是 void prepareForSegue UIStoryboardSegue segue sender id sender if s
  • UITextField 字体大小在开始或停止输入字符时发生变化

    我见过很多线程 但没有找到解决这个问题的任何具体解决方案 我使用 XIB 创建了 UITextfield 尚未以编程方式对该字段执行任何操作 一旦我开始打字 文本字段的字体就会改变 一旦我将焦点移出文本字段 并且随着键盘消失 字体大小就会减
  • 使用prefersLargeTitles 和 UITableView 平滑滚动

    我在使用时遇到了滚动问题prefersLargeTitles并添加了UITableView 如果我设置prefersLargeTitles在导航控制器中 其根是UITableViewController一切都很好 导航大标题的滚动方式与我们
  • 找不到导航对象。您的组件是否位于导航器屏幕内?

    在我下面的代码中 当我使用 useNavigation 时 它会给出像我的问题一样的错误 如何使用useNavigation 请任何人都可以解决此错误 错误 找不到导航对象 您的组件是否位于导航器屏幕内 我从这里跟踪了代码https rnf
  • macOS 应用程序中还需要 Swift 的嵌入式 dylib吗?

    Swift 5 发行说明说 对于运行 iOS 12 2 watchOS 5 2 和 tvOS 12 2 的设备 Swift 应用程序不再在构建变体中包含 Swift 标准库和 Swift SDK 覆盖的动态链接库 但在同一上下文中没有提及
  • Swift 3 '[UIApplicationLaunchOptionsKey:任意]?'无法转换为 '[String : NSString]'

    我有一个 TVOS 应用程序已从 Swift 2 转换为 Swift 3 但出现以下错误 我不确定如何让它安静下来 UIApplicationLaunchOptionsKey 任意 无法转换为 String NSString 它出现在这段代
  • macCatalyst/ SwiftUI 触摸栏

    如何在使用 SwiftUI 编写的 Catalyst Apps 中添加 Touch Bar 支持 例如 如果我想在视图中显示一个按钮 import SwiftUI struct ContentView View var body some
  • 为什么我们在 @synchronized 块中传递 self ?

    我猜 synchronized 块不依赖于对象 而是依赖于线程 对吗 既然如此 我们为什么要传递 self 呢 synchronized是语言提供的用于创建同步作用域的构造 因为使用简单的全局共享互斥锁效率非常低 因此序列化每个单独的互斥锁
  • 在 Swift 中将 Int 转换为 UInt32

    我正在制作一个 Tcp 客户端 因此使用CFStreamCreatePairWithSocketToHost它期望第二个参数为 UInt32 这是我正在尝试做的事情的示例 func initNetwork IP String Port In

随机推荐

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

    我正在制作一个应用程序滑动菜单 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