Core data 创建一个NSManagedObject并注入到SwiftUI视图中,该视图将自动添加到导航堆栈中

Core data 创建一个NSManagedObject并注入到SwiftUI视图中,该视图将自动添加到导航堆栈中,core-data,swiftui,Core Data,Swiftui,我的主应用程序屏幕显示对象列表(本例中的患者)。我想允许用户通过点击工具栏按钮添加新患者,该按钮允许用户添加相关详细信息 实现编辑视图非常简单,编辑现有患者也不会有任何问题。在列表视图中添加新患者也很容易,无需任何编辑视图的自动模式显示或导航。当然,缺点是用户需要手动执行一系列额外的步骤来编辑新添加的对象,这对用户不是很友好 如果我使用MVVM,这不是问题,但我正在尝试使用苹果自己的“推荐”方法,在主运行中心周围使用属性包装等。因为在视图初始化完成之前,我无法访问主运行中心,所以最简单的方法(如

我的主应用程序屏幕显示对象列表(本例中的患者)。我想允许用户通过点击工具栏按钮添加新患者,该按钮允许用户添加相关详细信息

实现编辑视图非常简单,编辑现有患者也不会有任何问题。在列表视图中添加新患者也很容易,无需任何编辑视图的自动模式显示或导航。当然,缺点是用户需要手动执行一系列额外的步骤来编辑新添加的对象,这对用户不是很友好

如果我使用MVVM,这不是问题,但我正在尝试使用苹果自己的“推荐”方法,在主运行中心周围使用属性包装等。因为在视图初始化完成之前,我无法访问主运行中心,所以最简单的方法(如果没有提供视图,则创建新患者)是不可能的。当前的策略(在父视图中创建新对象)导致状态不一致,并且每当您尝试编辑子视图中的字段时,就会触发NavigationLink,从而导致一些非常奇怪的行为

我确信我把这件事弄得不必要的复杂,并且仍然停留在一种必须的心态中,但是一些指导会有所帮助

struct PatientsView: View {
    @EnvironmentObject var dataController: DataController
    @Environment(\.managedObjectContext) var managedObjectContext
    static let tag = "patients"
    
    @State private var navLinkTag: Int? = 0
    
    let patients: FetchRequest<Patient>
    
    init() {
        patients = FetchRequest(entity: Patient.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Patient.creationDate, ascending: false)])
    }
    
    var body: some View {
        NavigationView {
            List {
                ForEach(patients.wrappedValue) { patient in
                    PatientRow(patient: patient)
                }
                .onDelete { offsets in
                    for offset in offsets {
                        let item = patients.wrappedValue[offset]
                        dataController.delete(item)
                    }
                }
            }
            .navigationTitle("Patients")
            .toolbar {
                ToolbarItem {
                    Button(action: {
                        navLinkTag = 1
                    }, label: {
                            Image(systemName: "plus")
                    })
                    .background(
                        NavigationLink(
                            destination: EditPatientView(patient: newPatient()),
                            tag: 1,
                            selection: $navLinkTag,
                            label: { EmptyView() })
                    )
                }
            }
        }
    }
    
    private func newPatient() -> Patient {
        let patient = Patient(context: managedObjectContext)
        patient.creationDate = Date()
        patient.name = "New patient"
        
        dataController.save()
        
        return patient
    }
}

struct PatientRow: View {
    @ObservedObject var patient: Patient
    
    var body: some View {
        HStack {
            NavigationLink(destination: PatientActivityView(patient: patient)) {
                VStack {
                    HStack {
                        Text(patient.patientName)
                        Text(patient.patientOwnerName)
                            .bold()
                        Spacer()
                    }
                    
                    HStack {
                        Text(patient.patientSpecies)
                        Text(patient.patientSex)
                        Text(patient.age)
                        Spacer()
                    }
                    .font(.caption)
                }
            }
        }
    }
}

struct EditPatientView: View {
    @EnvironmentObject var dataController: DataController
    @Environment(\.managedObjectContext) var managedObjectContext
    
    @State var owner: String
    @State var name: String
    @State var species: String
    @State var dob: Date
    @State var sex: String
    
    let patient: Patient
    
    init(patient: Patient) {
        self.patient = patient
        
        _owner = State(wrappedValue: self.patient.patientOwnerName)
        _name = State(wrappedValue: self.patient.patientName)
        _species = State(wrappedValue: self.patient.patientSpecies)
        _sex = State(wrappedValue: self.patient.patientSex)
        _dob = State(wrappedValue: self.patient.patientDateOfBirth)
    }
    
    
    var body: some View {
        Form {
            Section(header: Text("Identity")) {
                TextField("Name", text: $name.onChange(update))
                TextField("Owner", text: $owner.onChange(update))
            }
            
            Section(header: Text("Signalment")) {
                TextField("Species", text: $species.onChange(update))
                TextField("Sex", text: $sex.onChange(update))
                DatePicker("Date of birth",
                           selection: $dob.onChange(update),
                           in: ...Date(),
                           displayedComponents: .date)
                    .datePickerStyle(CompactDatePickerStyle())                }
        }
        .navigationTitle("Edit Patient")
    }
    
    
    private func update() {
        patient.name = name
        patient.owner = owner
        patient.species = species
        patient.sex = sex
        patient.dateOfBirth = dob
    }
}
struct PatientsView:View{
@EnvironmentObject变量dataController:dataController
@环境(\.managedObjectContext)变量managedObjectContext
静态let tag=“患者”
@国家私有变量navLinkTag:Int?=0
让患者:请求
init(){
patients=FetchRequest(实体:Patient.entity(),sortDescriptors:[NSSortDescriptor(键路径:\Patient.creationDate,升序:false)])
}
var body:一些观点{
导航视图{
名单{
ForEach(patients.wrappedValue){patient in
PatientRow(患者:患者)
}
.onDelete{中的偏移量
用于偏移中的偏移{
let item=patients.wrappedValue[offset]
dataController.delete(项目)
}
}
}
.navigationTitle(“患者”)
.工具栏{
工具栏项{
按钮(操作:{
navLinkTag=1
},标签:{
图像(系统名称:“plus”)
})
.背景(
导航链接(
目标:EditPatientView(患者:newPatient()),
标签:1,
选择:$navLinkTag,
标签:{EmptyView()})
)
}
}
}
}
private func newPatient()->Patient{
让患者=患者(上下文:managedObjectContext)
patient.creationDate=日期()
patient.name=“新患者”
dataController.save()
返回病人
}
}
结构PatientRow:视图{
@观察对象变量患者:患者
var body:一些观点{
HStack{
导航链接(目的地:PatientActivityView(患者:患者)){
VStack{
HStack{
文本(patient.patientName)
文本(patient.patientTownerName)
.bold()
垫片()
}
HStack{
文本(患者。患者种类)
文本(patient.patientSex)
文本(患者年龄)
垫片()
}
.font(.caption)
}
}
}
}
}
结构EditPatientView:视图{
@EnvironmentObject变量dataController:dataController
@环境(\.managedObjectContext)变量managedObjectContext
@状态变量所有者:字符串
@状态变量名称:String
@状态变量种类:字符串
@状态变量dob:日期
@状态变量性别:字符串
让病人:病人
初始(患者:患者){
病人
_所有者=状态(wrappedValue:self.patient.patientTownerName)
_name=State(wrappedValue:self.patient.patientName)
_种类=状态(wrappedValue:self.patient.patientSpecies)
_性别=状态(wrappedValue:self.patient.patientSex)
_dob=状态(wrappedValue:self.patient.patientDateOfBirth)
}
var body:一些观点{
形式{
节(标题:文本(“标识”)){
TextField(“Name”,text:$Name.onChange(更新))
TextField(“所有者”,text:$Owner.onChange(更新))
}
章节(标题:文本(“信号”)){
TextField(“物种”,text:$Species.onChange(更新))
TextField(“Sex”,text:$Sex.onChange(更新))
日期选择器(“出生日期”,
选择:$dob.onChange(更新),
在:…日期(),
displayedComponents:。日期)
.datePickerStyle(CompactDatePickerStyle())}
}
.navigationTitle(“编辑患者”)
}
私有函数更新(){
patient.name=姓名
patient.owner=所有者
种类
病人性别
患者出生日期=出生日期
}
}

您的问题在于新的托管对象和编辑对象的处理方式不同。我有不同的看法:

struct EditPatientView: View {
    @EnvironmentObject var dataController: DataController
    @Environment(\.managedObjectContext) var managedObjectContext
    
    @State private var owner: String
    @State private var name: String
    @State private var species: String
    @State private var dob: Date
    @State private var sex: String
    
    let patient: Patient
    
    init(patient: Patient) {
        self.patient = patient
        
        _owner = State(wrappedValue: self.patient.patientOwnerName)
        _name = State(wrappedValue: self.patient.patientName)
        _species = State(wrappedValue: self.patient.patientSpecies)
        _sex = State(wrappedValue: self.patient.patientSex)
        _dob = State(wrappedValue: self.patient.patientDateOfBirth)
    }
    
    
    var body: some View {
        Form {
            Section(header: Text("Identity")) {
                TextField("Name", text: $name.onChange(update))
                TextField("Owner", text: $owner.onChange(update))
            }
            
            Section(header: Text("Signalment")) {
                TextField("Species", text: $species.onChange(update))
                TextField("Sex", text: $sex.onChange(update))
                DatePicker("Date of birth",
                           selection: $dob.onChange(update),
                           in: ...Date(),
                           displayedComponents: .date)
                    .datePickerStyle(CompactDatePickerStyle())                }
        }
        .navigationTitle("Edit Patient")
    }
    
    
    private func update() {
        patient.setValue(self.name, forKey: "patientName")
        patient.setValue(self.owner, forKey: "patientOwnerName")
        patient.setValue(self.species, forKey: "patientSpecies")
        patient.setValue(self.sex, forKey: "patientSex")
        patient.setValue(self.dob, forKey: "patientDateOfBirth")
        
        do {
            try self.managedObjectContext.save()
        } catch {
            //Handle any error
        }
    }
}
对于新患者:

struct NewPatientView: View {
    @EnvironmentObject var dataController: DataController
    @Environment(\.managedObjectContext) var managedObjectContext
    
    @State private var owner: String = ""
    @State private var name: String = ""
    @State private var species: String = ""
    @State private var dob: Date = Date()
    @State private var sex: String = ""
        
    var body: some View {
        Form {
            Section(header: Text("Identity")) {
                TextField("Name", text: $name)
                TextField("Owner", text: $owner)
            }
            
            Section(header: Text("Signalment")) {
                TextField("Species", text: $species)
                TextField("Sex", text: $sex)
                DatePicker("Date of birth",
                           selection: $dob,
                           in: ...Date(),
                           displayedComponents: .date)
                    .datePickerStyle(CompactDatePickerStyle())
            }
            Button(action: {
                self.save()
            }) {
                Text("Save")
            }
        }
        .navigationTitle("New Patient")
    }
    
    
    private func save() {

        let patient = Patient(context: self.managedObjectContext)
        patient.name = name
        patient.owner = owner
        patient.species = species
        patient.sex = sex
        patient.dateOfBirth = dob

        do {
            try self.managedObjectContext.save()
        } catch {
            //Handle any error
        }
    }
}
我也会考虑让它自己的实体通过一种关系联系到每一个动物。否则,如果所有者有mo,您将不断复制他们
import SwiftUI

struct PatientsView: View {
    @EnvironmentObject var dataController: DataController
    @Environment(\.managedObjectContext) var managedObjectContext
    
    @State private var showEditPatient = false
    @State private var newPatient: Patient? = nil /// need to use @State as the PatientView FetchedResult will change when Patient is added or mutated causing the hierachy to be discarded and refreshed. A "normal" property will be re-created leading to multiple new Patient instances and warnings about state amendment during a view update
    
    private let patients: FetchRequest<Patient>
    
    init() {
        patients = FetchRequest(entity: Patient.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Patient.creationDate, ascending: false)])
    }
    
    var body: some View {
        let createNewPatient = Binding<Bool>( /// A custom binding seems to be the best way of creating new Patient instances only when required
            get: { return showEditPatient },
            set: { newValue in
                if !newValue {
                    newPatient = nil /// EditPatient view has been dismissed so discard the Patient
                } else {
                    if newPatient == nil { newPatient = createPatient() } /// New Patient required 
                }
                showEditPatient = newValue
            }
        )
        
        return NavigationView {
            List {
                ForEach(patients.wrappedValue) { patient in
                    PatientRow(patient: patient)
                }
                .onDelete { offsets in
                    for offset in offsets {
                        let item = patients.wrappedValue[offset]
                        dataController.delete(item)
                    }
                }
            }
            .navigationTitle("Patients")
            .toolbar {
                ToolbarItem {
                    Button(action: {
                        createNewPatient.wrappedValue = true
                    }, label: {
                        Image(systemName: "plus")
                    })
                }
            }
            .sheet(isPresented: $showEditPatient,
                   onDismiss: { createNewPatient.wrappedValue = false }) {
                EditPatientView(patient: newPatient!) /// Patient should never be nil - force unwrap will catch unexpected code path
            }
        }
    }
    
    private func createPatient() -> Patient {
        let patient = Patient(context: managedObjectContext)
        patient.creationDate = Date()
        patient.name = "New patient"
        
        dataController.save()
        
        return patient
    }
}

struct PatientsView_Previews: PreviewProvider {
    static let dataController = DataController.preview
    
    static var previews: some View {
        PatientsView()
            .environment(\.managedObjectContext, dataController.container.viewContext)
            .environmentObject(dataController)
    }
}