eri*_*icg 2 core-data ios swift swiftui
我在CDPassingQ有一个最小的示例项目
我的主要(ContentView)看起来像:
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)
}
}
Run Code Online (Sandbox Code Playgroud)
NameViewer看起来像:
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()
}
}
Run Code Online (Sandbox Code Playgroud)
我可以创建要显示在 ContentView 列表中的新项目。
然后,当我选择列表中的某个项目时,我将该项目传递给 NameViewer。我可以确认我在 .onAppear 代码中成功找到了正确的对象。
然而,存在两个问题:
如果我在列表中选择一个项目,则项目名称不会出现在名称文本字段中,除非我先单击文本字段。
使用 .onAppear 似乎不是放置该代码的正确位置。原因是选择器将另一个视图推送到堆栈上,一旦选择了该项目,.onAppear 就会再次运行,并且我会丢失对名称字段的更改名称。
如何更改代码来解决这些问题?
为了实现所需的功能,我将更改 UI 和核心数据方面的架构。
\n在用户界面方面,最好使用导航链接来显示静态数据详细视图,并使用模态来执行数据操作,例如创建和编辑对象。因此,使用一个视图来显示对象详细信息(例如NameViewer),使用另一个视图来编辑对象(例如NameEditor)。此外,将NSManagedObject子类的属性直接绑定到 SwiftUI 控件。不要\xe2\x80\x99t 创建额外的@State属性,然后复制这些值。你\xe2\x80\x99引入了共享状态,SwiftUI 就是要消除这种共享状态。
在核心数据方面,为了执行创建和更新操作,您需要使用子上下文。每当您\xe2\x80\x99创建或更新对象时,都会显示注入了子上下文的模式编辑器视图。这样,如果我们\xe2\x80\x99对我们的更改不满意,我们可以简单地忽略模态,并且更改会被神奇地丢弃,而无需调用rollback(),因为该子上下文会随视图一起被破坏。由于您\xe2\x80\x99 现在正在使用子上下文,所以不要\xe2\x80\x99 忘记将主视图上下文保存在某处,例如当用户导航出应用程序时。
因此,为了在代码中实现这一点,我们需要一些结构来存储新创建的对象以及它们的子上下文:
\nstruct CreateOperation<Object: NSManagedObject>: Identifiable {\n let id = UUID()\n let childContext: NSManagedObjectContext\n let childObject: Object\n \n init(with parentContext: NSManagedObjectContext) {\n let childContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)\n childContext.parent = parentContext\n let childObject = Object(context: childContext)\n \n self.childContext = childContext\n self.childObject = childObject\n }\n}\n\nstruct UpdateOperation<Object: NSManagedObject>: Identifiable {\n let id = UUID()\n let childContext: NSManagedObjectContext\n let childObject: Object\n \n init?(\n withExistingObject object: Object,\n in parentContext: NSManagedObjectContext\n ) {\n let childContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)\n childContext.parent = parentContext\n guard let childObject = try? childContext.existingObject(with: object.objectID) as? Object else { return nil }\n \n self.childContext = childContext\n self.childObject = childObject\n }\n}\nRun Code Online (Sandbox Code Playgroud)\nUI代码如下:
\nstruct ContentView: View {\n @Environment(\\.managedObjectContext) private var viewContext\n @FetchRequest(\n sortDescriptors: [NSSortDescriptor(keyPath: \\Item.name, ascending: true)], animation: .default\n ) private var items: FetchedResults<Item>\n @State private var itemCreateOperation: CreateOperation<Item>?\n \n var body: some View {\n NavigationView {\n List {\n ForEach(items) { item in\n NavigationLink {\n NameViewer(item: item)\n } label: {\n Text(item.name ?? "")\n }\n }\n }\n .toolbar {\n ToolbarItemGroup(placement: .navigationBarTrailing) {\n EditButton()\n Button(action: {\n itemCreateOperation = CreateOperation(with: viewContext)\n }) {\n Label("Add Item", systemImage: "plus")\n }\n }\n }\n .sheet(item: $itemCreateOperation) { createOperation in\n NavigationView {\n NameEditor(item: createOperation.childObject)\n .navigationTitle("New Item")\n }\n .environment(\\.managedObjectContext, createOperation.childContext)\n }\n }\n }\n}\n\nstruct NameViewer: View {\n @Environment(\\.managedObjectContext) private var viewContext\n @State private var itemUpdateOperation: UpdateOperation<Item>?\n \n @ObservedObject var item: Item\n \n var body: some View {\n Form {\n Section {\n Text(item.name ?? "")\n }\n }\n .navigationTitle("Item")\n .toolbar {\n Button("Update") {\n itemUpdateOperation = UpdateOperation(withExistingObject: item, in: viewContext)\n }\n }\n .sheet(item: $itemUpdateOperation) { updateOperation in\n NavigationView {\n NameEditor(item: updateOperation.childObject)\n .navigationTitle("Update Item")\n }\n .environment(\\.managedObjectContext, updateOperation.childContext)\n }\n }\n}\n\nstruct NameEditor: View {\n @Environment(\\.dismiss) private var dismiss\n @Environment(\\.managedObjectContext) private var childContext\n \n @ObservedObject var item: Item\n \n var body: some View {\n Form {\n Section(header: Text("Information")) {\n if let name = Binding($item.name) {\n TextField("Name", text: name)\n }\n }\n }\n .toolbar {\n Button() {\n try? childContext.save()\n dismiss()\n } label: {\n Text("Save")\n }\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n更多信息请参见我的相关回答:
\n\n| 归档时间: |
|
| 查看次数: |
1076 次 |
| 最近记录: |