Here is a simplified version of what I think you are trying to do. It uses code from a SwiftUI sample project. Just create an Xcode SwiftUI project with CoreData.
import SwiftUI
import CoreData
//Standard List Screen where you can select an item to see/edit and you find a button to add
struct ReusableParentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
//Keeps work out of the Views so it can be reused
@StateObject var vm: ReusableParentViewModel = ReusableParentViewModel()
var body: some View {
NavigationView{
List{
ForEach(items) { item in
NavigationLink {
//This is the same view as the sheet but witht he item passed fromt he list
ReusableItemView(item: item)
} label: {
VStack{
Text(item.timestamp.bound, formatter: itemFormatter)
Text(item.hasChanges.description)
}
}
}.onDelete(perform: { indexSet in
for idx in indexSet{
vm.deleteItem(item: items[idx], moc: viewContext)
}
})
}
//Show sheet to add new item
.sheet(item: $vm.newItem, onDismiss: {
vm.saveContext(moc: viewContext)
//You can also cancel/get rid of the new item/changes if the user doesn't save
//vm.cancelAddItem(moc: viewContext)
}, content: { newItem in
NavigationView{
ReusableItemView(item: newItem)
}
//Inject the VM the children Views have access to the functions
.environmentObject(vm)
})
.toolbar(content: {
ToolbarItem(placement: .automatic, content: {
//Trigger new item sheet
Button(action: {
vm.addItem(moc: viewContext)
}, label: {
Image(systemName: "plus")
})
})
})
}
//Inject the VM the children Views have access to the functions
.environmentObject(vm)
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
}
//The Item's View
struct ReusableItemView: View {
//All CoreData objects are ObservableObjects to see changes you have to wrap them in this
@ObservedObject var item: Item
@Environment(\.editMode) var editMode
var body: some View {
VStack{
if editMode?.wrappedValue == .active{
EditItemView(item: item)
}else{
ShowItemView(item: item)
}
}
.toolbar(content: {
ToolbarItem(placement: .automatic, content: {
//If you want to edit this info just press this button
Button(editMode?.wrappedValue == .active ? "done": "edit"){
if editMode?.wrappedValue == .active{
editMode?.wrappedValue = .inactive
}else{
editMode?.wrappedValue = .active
}
}
})
})
}
}
//The View to just show the items info
struct ShowItemView: View {
//All CoreData objects are ObservableObjects to see changes you have to wrap them in this
@ObservedObject var item: Item
var body: some View {
if item.timestamp != nil{
Text("Item at \(item.timestamp!)")
}else{
Text("nothing to show")
}
}
}
//The View to edit the item's info
struct EditItemView: View {
@Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject var vm: ReusableParentViewModel
@Environment(\.editMode) var editMode
//All CoreData objects are ObservableObjects to see changes you have to wrap them in this
@ObservedObject var item: Item
var body: some View {
DatePicker("timestamp", selection: $item.timestamp.bound).datePickerStyle(GraphicalDatePickerStyle())
}
}
struct ReusableParentView_Previews: PreviewProvider {
static var previews: some View {
ReusableParentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
class ReusableParentViewModel: ObservableObject{
//Can be used to show a sheet when a new item is created
@Published var newItem: Item? = nil
//If you dont want to create a CoreData item immediatly just present a sheet with the AddItemView in it
@Published var presentAddSheet: Bool = false
func addItem(moc: NSManagedObjectContext) -> Item{
//You should never create an ObservableObject inside a SwiftUI View unless it is using @StateObject which doesn't apply to a CoreData object
let temp = Item(context: moc)
temp.timestamp = Date()
//Sets the newItem variable
newItem = temp
//And returns the new item for other uses
return temp
}
func cancelAddItem(moc: NSManagedObjectContext){
rollbackChagnes(moc: moc)
newItem = nil
}
func rollbackChagnes(moc: NSManagedObjectContext){
moc.rollback()
}
func deleteItem(item: Item, moc: NSManagedObjectContext){
moc.delete(item)
saveContext(moc: moc)
}
func saveContext(moc: NSManagedObjectContext){
do{
try moc.save()
}catch{
print(error)
}
}
}
And if for some reason you don't want to create a CoreData object ahead of time which seems to be what you are doing you can always Create the temp variables and make a sharable editable view that takes in @Binding
for each variable you want to edit.
//The View to Add the item's info, you can show this anywhere.
struct AddItemView: View {
@Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject var vm: ReusableParentViewModel
//These can be temporary variables
@State var tempTimestamp: Date = Date()
var body: some View {
EditableItemView(timestamp: $tempTimestamp)
.toolbar(content: {
ToolbarItem(placement: .navigationBarLeading, content: {
//Create and save the item
Button("save"){
let new = vm.addItem(moc: viewContext)
new.timestamp = tempTimestamp
vm.saveContext(moc: viewContext)
}
})
})
}
}
//The View to edit the item's info
struct EditItemView: View {
@EnvironmentObject var vm: ReusableParentViewModel
@Environment(\.managedObjectContext) private var viewContext
@ObservedObject var item: Item
var body: some View {
VStack{
EditableItemView(timestamp: $item.timestamp.bound)
.onDisappear(perform: {
vm.rollbackChagnes(moc: viewContext)
})
//Just save the item
Button("save"){
vm.saveContext(moc: viewContext)
}
}
}
}
//The View to edit the item's info
struct EditableItemView: View {
@Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject var vm: ReusableParentViewModel
//All CoreData objects are ObservableObjects to see changes you have to wrap them in this
@Binding var timestamp: Date
var body: some View {
DatePicker("timestamp", selection: $timestamp).datePickerStyle(GraphicalDatePickerStyle())
}
}