Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
891 views
in Technique[技术] by (71.8m points)

core data - Is viewContext different between Swift and SwiftUI

EDIT: I've added a rewording of my question at the bottom.

So I have an app that I've been working on for a long time. I used it to teach myself Xcode / Swift and now that I have a Mac again, I'm trying to learn SwiftUI as well by remaking the app completely as a new project.

I have an Entity in CoreData that only ever contains 1 record, which has all of the attributes of the settings I'm tracking.

Before, I was able to make an extension to my Entity, Ent_Settings that would allow me to always get that specific record back.

class var returnSettings: Ent_Settings {
    //this is inside of extension Ent_Settings
    
    let moc = PersistenceController.shared.container.viewContext
    var settings: Ent_Settings?
    let fetchRequest: NSFetchRequest<Ent_Settings> = Ent_Settings.fetchRequest()
    
    do {
        let results = try moc.fetch(fetchRequest)
        
        if results.count == 0 {
            Ent_Settings.createSettingsEntity()
            
            do {
                let results = try moc.fetch(fetchRequest)
                settings = results.first!
            } catch {
                error.tryError(tryMessage: "Failed performing a fetch to get the Settings object after it was created.", loc: "Ent_Settings Extension")
            }
        } else {
            settings = results.first!
        }
    } catch {
        error.tryError(tryMessage: "Fetching Settings Object", loc: "Ent_Settings Extension")
    }
    
    return settings!
}

It took me a while to figure out how to get the moc now that there is no AppDelegate, but you can see I figured out in the code and that seems to work great.

However, when I go to a View that I made in SwiftUI, I'm getting weird results. In the Simulator, I can run my app and see the changes. Leave the view and come back and see the changes are saved. However, if I rebuild and run the app it loses all data. So it seems it's not remembering things after all. Basically, it's volatile memory instead of persistent memory. But if I put another function in the extension of Ent_Settings to update that attribute and then build and run again, it will persistently save.

I'm wondering if it has something to do with how the context is managed, perhaps it's not seeing the changes since it is a different "listener"?

Here is what I'm doing to make the changes in the View. (I tried removing the extra stuff that just made it harder to see)

import SwiftUI

struct Settings_CheckedView: View {
    
    @Environment(.managedObjectContext) private var viewContext
    @State private var picker1 = 0
    
    var body: some View {
        Form {
            Section(header: Text("..."),
                    footer: Text("...")) {
                VStack {
                    HStack { ... }
                    Picker("", selection: $picker1) {
                        Text("Off").tag(0)
                        Text("Immediately").tag(1)
                        Text("Delay").tag(2)
                    }
                    .pickerStyle(SegmentedPickerStyle())
                    .onChange(of: picker1, perform: { value in
                        
                        settings.autoDeleteSegment = Int16(value)
                        
                        viewContext.trySave("Updating auto-delete segment value in Settings", 
                                            loc: "Ent_Settings Extension")
                    })
                }
            }
        ...
        ...
        .onAppear(perform: {
            settings = Ent_Settings.returnSettings
            
            self.picker1 = Int(settings.autoDeleteSegment)
            self.picker2 = Int(settings.deleteDelaySegment)
        })
    }
}

And here is the code that I'm using for the trySave() function I have on the viewContext

extension NSManagedObjectContext {
    
    func trySave(_ tryMessage: String, loc: String) {
        
        let context = self
        if context.hasChanges {
            do {
                try self.save()
            } catch {
                print("Attempted: (tryMessage) -in (loc)")
                let nsError = error as NSError
                fatalError("Unresolved error (nsError), (nsError.userInfo)")
            }
        }
    }
}

This code doesn't seem to actually save anything to the Database.

I also tried adding this code into Settings_CheckedView to see if that would make a difference, but I got even weird results.

@FetchRequest(sortDescriptors:[])
private var settingsResult: FetchedResults<Ent_Settings>

But that returns zero results even though I know the Ent_Settings has 1 record. So that makes me wonder if I am actually creating two databases with the way I'm doing it.

I'm really new to SwiftUI and CoreData seems to be one of those things that is hard to get information about. Hoping someone can point out where I'm going wrong. I'd love to be able to make CoreData changes in both Swift and SwiftUI.

Thank you for any help you can give and let me know if you need anything else added in.

EDITED: To try to reword question

So ultimately, I'm wanting to do some stuff with CoreData inside of the extension for that entity. My database has a lot of aspects to it, but I also made an entity in there to store settings information and since that one isn't related to anything else, I figured that would be a good starting point in learning SwiftUI. I have everything working in UIKit, but I'm specifically trying to learn SwiftUI as well.

From how I understand SwiftUI, it is meant to replace the storyboard and make the lifecycle stuff much easier. So classes I have that deal with CoreData should still do that external to SwiftUI.

As you can see above in my Settings_CheckedView view above, I'm referencing that single settings record from an extension in Ent_Settings. Basically, everything inside of that extension takes care of returning that single record of Settings, checking if that record exists and creating it if it doesn't (first time running app basically)

Ideally, I'd like to keep the functionality inside the extension of Ent_Settings, but my problem is I can't get the correct instance of the moc to persistently save it.

@main
struct MyApp: App {
    let persistenceController = PersistenceController.shared

    var body: some Scene {
        WindowGroup {
            MainMenuView().onAppear(perform: {
                if Ent_Lists.hasAnyList(persistenceController.container.viewContext) == false {
                    Ent_Lists.createAnyList(persistenceController.container.viewContext)
                }
                
                if Ent_Section.hasBlankSection(persistenceController.container.viewContext) == false {
                    Ent_Section.createBlankSection(persistenceController.container.viewContext)
                }
                
                //self.settings = Ent_Settings.returnSettings(persistenceController.container.viewContext)
            })
            //ContentView()
                //.environment(.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

I'm pretty certain that let persistenceController = PersistenceController.shared is initializing the persistent controller used throughout the app. Either creating it or retrieving it.

So, I tried this first inside of Ent_Settings: let moc = PersistenceController.shared.container.viewContext but I think that this might be creating a new PersistenceController outside the one made inside of MyApp

I also tried let moc = EZ_Lists_SwiftUI.PersistenceController.shared.container.viewContext but I'm pretty sure that also makes a new instance given I can only access the upper case PersistenceController and not the lower case one.

I also tried just passing the moc from the view like this: class func createSettingsEntity(_ moc: NSManagedObjectContext) { but I get an error about the moc being nil, so I'm guessing the moc can't be sent by reference from a Struct.

Thread 1: "+entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name 'Ent_Settings'"

And just to be clear, I'm adding this to the view: @Environment(.managedObjectContext) private var viewContext adding this @State property to the View: @State private var settings: Ent_Settings! and setting it within .onAppear with this: self.settings = Ent_Settings.returnSettings(self.viewContext)

So what I'm really looking for is how I access the moc from within that extension of Ent_Settings. In my app that was purely UIKit and Storyboards I used this: let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext but as there is no longer an AppDelegate, I cannot use that anymore. What replaces this to get moc when you are doing SwiftUI but working outside of the Views, like in a class or something like I am? Thank you. Hopefully this extra information helps explain my issue.

question from:https://stackoverflow.com/questions/65866389/is-viewcontext-different-between-swift-and-swiftui

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

If you are only using coredata to store settings, I would look into UserDefaults and if that matches your use case. https://developer.apple.com/documentation/foundation/userdefaults If you for some reason need to use CoreData, I would recommend enabling CloudKit, as CloudKit will allow you to actually view what data is saved via a web console.

Also, SwiftUI does not create an AppDelegate for you, you are still able to just create your own AppDelegate.swift file as you would with UIKit. Just define the @UIApplicationDelegateAdaptor to your custom AppDelegate.swift file in your top most struct (the one with @main) before the struct declaration.

  @main
    struct MainAppView: App {
        @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...