swiftui how to fetch core data values from Detail to Edit views

Here is a simplified version of your code Just paste this code into your project and call YourAppParent() in a body somewhere in your app as high up as possible since it creates the container.

import SwiftUI
import CoreData
//Class to hold all the Persistence methods
class CoreDataPersistence: ObservableObject{
    //Use preview context in canvas/preview
    let context = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" ? PersistenceController.preview.container.viewContext : PersistenceController.shared.container.viewContext
    
    ///Creates an NSManagedObject of **ANY** type
    func create<T: NSManagedObject>() -> T{
        T(context: context)
        //For adding Defaults see the `extension` all the way at the bottom of this post
    }
    ///Updates an NSManagedObject of any type
    func update<T: NSManagedObject>(_ obj: T){
        //Make any changes like a last modified variable
        //Figure out the type if you want type specific changes
        if obj is FileEnt{
            //Make type specific changes
            let name = (obj as! FileEnt).fileName
            print("I'm updating FileEnt \(name ?? "no name")")
        }else{
            print("I'm Something else")
        }
        
        save()
    }
    ///Creates a sample FileEnt
    //Look at the preview code for the `FileEdit` `View` to see when to use.
    func addSample() -> FileEnt{
        let sample: FileEnt = create()
        sample.fileName = "Sample"
        sample.fileDate = Date.distantFuture
        return sample
    }
    ///Deletes  an NSManagedObject of any type
    func delete(_ obj: NSManagedObject){
        context.delete(obj)
        save()
    }
    func resetStore(){
        context.rollback()
        save()
    }
    func save(){
        do{
            try context.save()
        }catch{
            print(error)
        }
    }
}
//Entry Point
struct YourAppParent: View{
    @StateObject var coreDataPersistence: CoreDataPersistence = .init()
    var body: some View{
        FileListView()
            //@FetchRequest needs it
            .environment(\.managedObjectContext, coreDataPersistence.context)
            .environmentObject(coreDataPersistence)
    }
}
struct FileListView: View {
    @EnvironmentObject var persistence: CoreDataPersistence
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \FileEnt.fileDate, ascending: true)],
        animation: .default)
    private var allFiles: FetchedResults<FileEnt>
    
    var body: some View {
        NavigationView{
            List{
                //Has to be lazy or it will create a bunch of objects because the view gets preloaded
                LazyVStack{
                    NavigationLink(destination: FileAdd(), label: {
                        Text("Add file")
                        Spacer()
                        Image(systemName: "plus")
                    })
                }
                ForEach(allFiles) { aFile in
                    NavigationLink(destination: FileDetailView(aFile: aFile)) {
                        Text(aFile.fileDate?.description ?? "no date")
                    }.swipeActions(edge: .trailing, allowsFullSwipe: true, content: {
                        Button("delete", role: .destructive, action: {
                            persistence.delete(aFile)
                        })
                    })
                }
            }
        }
    }
}
struct FileListView_Previews: PreviewProvider {
    static var previews: some View {
        YourAppParent()
//            let pers = CoreDataPersistence()
//            FileListView()
//                @FetchRequest needs it
//                .environment(\.managedObjectContext, pers.context)
//                .environmentObject(pers)
    }
}
struct FileDetailView: View {
    @EnvironmentObject var persistence: CoreDataPersistence
    @ObservedObject var aFile: FileEnt
    @State var showingFileEdit: Bool = false
    
    var body: some View{
        Form {
            Text(aFile.fileName ?? "")
        }
        Button(action: {
            showingFileEdit.toggle()
        }, label: {
            Text("Edit")
        })
            .sheet(isPresented: $showingFileEdit, onDismiss: {
                //Discard any changes that were not saved
                persistence.resetStore()
            }) {
                FileEdit(aFile: aFile)
                    //sheet needs reinject
                    .environmentObject(persistence)
            }
    }
}

///A Bridge to FileEdit that creates the object to be edited
struct FileAdd:View{
    @EnvironmentObject var persistence: CoreDataPersistence
    //This will not show changes to the variables in this View
    @State var newFile: FileEnt? = nil
    var body: some View{
        Group{
            if let aFile = newFile{
                FileEdit(aFile: aFile)
            }else{
                //Likely wont ever be visible but there has to be a fallback
                ProgressView()
                    .onAppear(perform: {
                        newFile = persistence.create()
                    })
            }
        }
        .navigationBarHidden(true)
        
    }
}
struct FileEdit: View {
    @EnvironmentObject var persistence: CoreDataPersistence
    @Environment(\.dismiss) var dismiss
    //This will observe changes to variables
    @ObservedObject var aFile: FileEnt
    var viewHasIssues: Bool{
        aFile.fileDate == nil || aFile.fileName == nil
    }
    var body: some View{
        Form {
            TextField("required", text: $aFile.fileName.bound)
            //DatePicker can give the impression that a date != nil
            if aFile.fileDate != nil{
                DatePicker("filing date", selection: $aFile.fileDate.bound)
            }else{
                //Likely wont ever be visible but there has to be a fallback
                ProgressView()
                    .onAppear(perform: {
                        //Set Default
                        aFile.fileDate = Date()
                    })
            }
        }
        
        Button("save", role: .none, action: {
            persistence.update(aFile)
            dismiss()
        }).disabled(viewHasIssues)
        Button("cancel", role: .destructive, action: {
            persistence.resetStore()
            dismiss()
        })
    }
}

extension Optional where Wrapped == String {
    var _bound: String? {
        get {
            return self
        }
        set {
            self = newValue
        }
    }
    var bound: String {
        get {
            return _bound ?? ""
        }
        set {
            _bound = newValue
        }
    }
    
}
extension Optional where Wrapped == Date {
    var _bound: Date? {
        get {
            return self
        }
        set {
            self = newValue
        }
    }
    public var bound: Date {
        get {
            return _bound ?? Date.distantPast
        }
        set {
            _bound = newValue
        }
    }
}

For adding a preview that requires an object you can use this code with the new CoreDataPersistence

///How to create a preview that requires a CoreData object.
struct FileEdit_Previews: PreviewProvider {
    static let pers = CoreDataPersistence()
    static var previews: some View {
        VStack{
            FileEdit(aFile: pers.addSample()).environmentObject(pers)
        }
    }
}

And since the create() is now generic you can use the Entity’s extension to add defaults to the variables.

extension FileEnt{ 
    public override func awakeFromInsert() {
        //Set defaults here
        self.fileName = ""
        self.fileDate = Date()
    }
}

Leave a Comment