I have an entity called Cart, but it seems I can't get it to save a new data from my viewmodel.
Persistence.swift
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
}
for _ in 0..<10 {
let newCart = Cart(context: viewContext)
newCart.menuName = "name"
newCart.menuImg = "img"
newCart.menuCode = "code"
}
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "Project")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
func save() {
let context = container.viewContext
if context.hasChanges {
print("Found Changes")
do {
try context.save()
} catch {
print("ERROR")
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
} else {
print("NO Changes")
}
}
}
MenuViewModel.swift
class MenuViewModel.swift: ObservableObject, MenuDetailService {
#Environment(\.managedObjectContext) var managedObjectContext
func addToCart() {
let newCart = Cart(context: managedObjectContext)
newCart.menuCode = menuCode
newCart.menuName = menuName
newCart.menuImg = menuImg
PersistenceController.shared.save()
}
}
ProjectApp.swift
#main
struct ProjectApp: App {
#Environment(\.scenePhase) var scenePhase
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}.onChange(of: scenePhase) { _ in
persistenceController.save()
}
}
}
The persistence controller func save() always print no changes, seems like I use different context? and I don't know how to use the context in the persistence controller to save the new data. How do I do this? Thank you very much.
//Remove the `.swift`
class MenuViewModel: ObservableObject, MenuDetailService {
//#Environment does not work well outside of a struct :View . Updates are unreliable
var managedObjectContext = PersistenceController.shared.container.viewContext
// You need to pass the variables somehow to the new object when you call the method from the `View`
func addToCart(menuCode: String, menuName: String, menuImg: UIImage) {
let newCart = Cart(context: managedObjectContext)
newCart.menuCode = menuCode
newCart.menuName = menuName
newCart.menuImg = menuImg
PersistenceController.shared.save()
}
}
I am not sure about that tutorial, I have not looked into it, but as I have used CoreData with SwiftUI, this was nothing I have seen:
let newCart = Cart(context: managedObjectContext)
newCart.menuCode = menuCode
newCart.menuName = menuName
newCart.menuImg = menuImg
PersistenceController.shared.save()
Rather what I would expect is that you replace the last line there with this:
do {
try managedObjectContext.save()
} catch {
print(error.localizedDescription)
}
Related
I created a simple SwiftUI app with Core Data and I want to be able to add data via the shortcuts app, I created a shortcut that takes some text as input and returns it in uppercase and when I run the shortcut in the shortcuts app, it works, however when I added an "add" function (to save data in the Core Data database) to the intent handle function, and I run it again nothing is saved in the app, here is the code:
class MakeUppercaseIntentHandler: NSObject, MakeUppercaseIntentHandling {
let persistenceController = PersistenceController()
func handle(intent: MakeUppercaseIntent, completion: #escaping (MakeUppercaseIntentResponse) -> Void) {
if let inputText = intent.text {
let uppercaseText = inputText.uppercased()
completion(MakeUppercaseIntentResponse.success(result: add(text: uppercaseText)))
} else {
completion(MakeUppercaseIntentResponse.failure(error: "The text entred is invalid"))
}
}
func resolveText(for intent: MakeUppercaseIntent, with completion: #escaping (MakeUppercaseTextResolutionResult) -> Void) {
if let text = intent.text, !text.isEmpty {
completion(MakeUppercaseTextResolutionResult.success(with: text))
} else {
completion(MakeUppercaseTextResolutionResult.unsupported(forReason: .noText))
}
}
func add(text: String) -> String{
let newItem = Item(context: persistenceController.container.viewContext)
newItem.text = text
do {
try persistenceController.container.viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return text
}
}
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init() {
container = NSPersistentContainer(name: "SiriShort")
guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.SiriShortcut2")?.appendingPathComponent("SiriShort.sqlite") else {
fatalError("Shared file container could not be created.")
}
let storeDescription = NSPersistentStoreDescription(url: fileContainer)
storeDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
storeDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.persistentStoreDescriptions = [storeDescription]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
}
}
Thank you
In order to initiate the coredata stack in app delegate is not recommended as it will create issue in a multi threaded environment. Therefore, I created a separate class for coredata stack and then a separate class to handle operations. And in the Coredata stack class it crashes. What am I missing here ?
CoreData Stack Class
Code looks like this
class CoreDataManager {
static let sharedManager = CoreDataManager()
private init() {} //Singleton
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "MyDatabase")
container.loadPersistentStores(completionHandler: { (_, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
lazy var viewContext: NSManagedObjectContext = {
return self.persistentContainer.viewContext
}()
lazy var cacheContext: NSManagedObjectContext = {
return self.persistentContainer.newBackgroundContext()
}()
lazy var updateContext: NSManagedObjectContext = {
let _updateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
_updateContext.parent = self.viewContext
return _updateContext
}()
}
This is how I access it
class StorageManager: NSObject {
lazy var managedObjectContext: NSManagedObjectContext = {
return CoreDataManager.sharedManager.persistentContainer.viewContext
}()
lazy var privateMOC: NSManagedObjectContext = {
return CoreDataManager.sharedManager.updateContext
}()
private func synchronize(privateMOC: NSManagedObjectContext) {
do {
try privateMOC.save()
self.managedObjectContext.performAndWait {
do {
try self.managedObjectContext.save()
// "Saved to main context"
} catch {
print("Could not synchonize data. \(error), \(error.localizedDescription)")
}
}
} catch {
print("Could not synchonize data. \(error), \(error.localizedDescription)")
}
}
}
A sample operation would look like this
func deleteContact(contactID: String) {
let privateMOC = self.privateMOC
privateMOC.performAndWait {
let request: NSFetchRequest<Contact> = Contact.fetchRequest()
request.predicate = NSPredicate(format: "contact_id= %#", contactID)
let result = try? privateMOC.fetch(request)
for object in result! {
privateMOC.delete(object)
}
self.synchronize(privateMOC: privateMOC)
}
}
hello i'm try to implement Core Data and swiftUI for my project.
I created an Entity called Aeroporti, which contain 2 attributes iataAPT : String and icaoAPT: string.
I want to save simple data on that.
I setup the app delegate like this:
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "testFirebaseCoreData")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
my content view list the field icaoAPT in the entity Aeroporti
import SwiftUI
import CoreData
struct ContentView: View {
#ObservedObject var dm : DataManager
#Environment(\.managedObjectContext) var managedObjectContext
#FetchRequest(entity: Aeroporti.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Aeroporti.icaoAPT, ascending: true)]) var aeroport : FetchedResults<Aeroporti> // crea la variabile che contiene le ricette
var body: some View {
List{
HStack{
// NOT WORKING IF USING DataMANAGER
// Button(action: {
// self.dm.provaSalva()
// }) {
// Text("save from manager")
// }
saveFromView // button save from view
}
ForEach(aeroport, id: \.self) { apt in
Text(apt.icaoAPT ?? "sconosciuto")
// .environment(\.managedObjectContext, self.managedObjectContext)
}
}
}
var saveFromView : some View {
Button(action: {
let apt = Aeroporti(context: self.managedObjectContext)
apt.iataAPT = "MFM"
apt.icaoAPT = "VMMC"
do {
try self.managedObjectContext.save()
print("airport saved.")
} catch {
print(error.localizedDescription)
}
}) {
Text("save fromview")
}
}
}
all working fine and the data is saved if I use the button (saveFromView) in the ContentView.
now, in order to better handle the data I have create a DataManager where I put the same code to save the data in the entity, and I try to lunch this code on the content view via a button (save from manager) but it doest work, i'm getting the error "The operation couldn’t be completed. (Foundation._GenericObjCError error 0.)" when it save.
import Combine
import SwiftUI
import CoreData
class DataManager: ObservableObject {
let objectWillChange = PassthroughSubject<Void, Never>()
#Environment(\.managedObjectContext) var managedObjectContext
func provaSalva() {
let apt = Aeroporti(context: managedObjectContext)
apt.iataAPT = "MFM"
apt.icaoAPT = "VMMC"
do {
try self.managedObjectContext.save()
print("airport saved.")
} catch {
print(error.localizedDescription)
}
}
}
the question is?? why.. is the same code... how can I solve this issue if I want to handle the save data not in the ContentView.
thanks for the help..
Give this a try. This is the implementation I use for my app.
class DataManager {
//MARK: - Setup
//Singleton object
static let defaults = DataManager()
private init() {}
//Managed object context
var moc: NSManagedObjectContext {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
//Save context, if changes were made
func save() {
if moc.hasChanges {
do {
print("Sucess!!")
try moc.save()
} catch {
print("Error while saving managedObjectContext \(error)")
}
}
}
//MARK: - Insert
//What you want to save
func provaSalva() {
let apt = Aeroporti(context: moc)
apt.iataAPT = "MFM"
apt.icaoAPT = "VMMC"
save()
}
}
Your saveFromView:
var saveFromView : some View {
Button(action: {
DataManager.defaults.provaSalva()
}) {
Text("save fromview")
}
}
solved by passing the db to the function
func provaSalva(dbCore: NSManagedObjectContext) {
let apt = Aeroporti(context: dbCore)
apt.iataAPT = "MFM"
apt.icaoAPT = "VMMC"
do {
try dbCore.save()
print("airport saved.")
} catch {
print(error.localizedDescription)
}
}
I'm trying to save a string array to core data, but every time I try to read the array a get an empty one(I'm using NSString because of an answer I've read here, but it doesn't seem to work). Here is the code I use for saving :
let i = UserDefaults.standard.integer(forKey: "Index")
let fetchRequest: NSFetchRequest<PersonalTask> = PersonalTask.fetchRequest()
do {
let tasks = try PersistanceService.context.fetch(fetchRequest)
self.personalTasks = tasks
} catch {
print("Error")
}
let pTask = personalTasks[i]
var subtasks = pTask.subtasks
print(subtasks as Any)
let subtask = textView!.text as NSString
subtasks?.append(subtask)
print(subtasks)
PersistanceService.saveContext()
Here is the function saveContext() :
static var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
static var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "Tasks")
guard let description = container.persistentStoreDescriptions.first else {
fatalError("No descriptions found")
}
description.setOption(true as NSObject, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}()
// MARK: - Core Data Saving support
static func saveContext () {
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
print("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
And here I am reading the data and getting an empty array :
let i = UserDefaults.standard.integer(forKey: "Index")
let fetchRequest: NSFetchRequest<PersonalTask> = PersonalTask.fetchRequest()
do {
let tasks = try PersistanceService.context.fetch(fetchRequest)
self.personalTasks = tasks
} catch {
print("Error")
}
let pTask = personalTasks[i]
print(pTask.title)
print(pTask.notes)
print(pTask.subtasks)
subtasks = pTask.subtasks as [String]? ?? [NSString]() as [String]
print(subtasks)
What should I do to fix this?
I'm trying to create a singleton class which works with an NSManagedObjectContext.
This is the class:
import Foundation
import CoreData
class PersistenceService{
init(){}
// MARK: - Core Data stack
static var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "frazeit")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
static func saveContext () {
let mainContext = persistentContainer.viewContext
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = mainContext
privateContext.perform {
if privateContext.hasChanges {
do {
try privateContext.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
}
In some occasion, it does not push changes into the persistent store, while the app is open the persistent container is changed but when I re-run the app changes are gone. What's the right way to save the changes into the persistent store.
This the class that does not work properly:
class func add(word: String, quotes:[Quotes], language: String){
for item in quotes {
if let phrase = item.phrase, let author = item.author {
let quote = CachedQuotes(context: PersistenceService.context)
quote.phrase = phrase
quote.date = Date() as NSDate
quote.keyword = word
quote.language = language
quote.author = author
PersistenceService.saveContext()
}
}
}
I call it to save quotes which are fetched from the network:
override func viewDidLoad() {
let quotes = CachedQuotes.getAllQuotes()
//Prints the number of saved records which is 0 now
self.getQuote { (result, error) in
if let qoutes = result?.quotes {
CachedQuotes.add(word: "friend", quotes: qoutes, language: "en")
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let quotes = CachedQuotes.getAllQuotes()
//Prints the number of saved records which is 10 now
}
But when I re-run the app, nothing is saved into the persistance container.
UPDATE:
The code below works now
static func saveContext () {
let mainContext = persistentContainer.viewContext
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.automaticallyMergesChangesFromParent = true
privateContext.parent = mainContext
privateContext.perform {
do {
try privateContext.save()
mainContext.perform({
do {
try mainContext.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
})
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
First it saves the private quoue then saves the main.
let mainContext = persistentContainer.viewContext
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = mainContext
You edit a context and then save the same context to persist the changes. Creating a child context to .viewContext and saving said child context does not save the .viewContext itself, where you made changes.
If you want to use background queues, first set var automaticallyMergesChangesFromParent: Bool on the .viewContext where you want to receive changes from the background queue. Then you create a background context, set on it the same persistentStoreCoordinator from .viewContext, make changes on it and then save the background queue.
Using privateContext.perform is a good start. You can do better if you wrap the changes to quote in a perform through the context in which the quote was created in the first place, so you access quote through the same thread the context uses.
Here is the singleton from Apple's Refreshing and Maintaining Your App Using Background Tasks sample.
import Foundation
import CoreData
class PersistentContainer: NSPersistentContainer {
private static let lastCleanedKey = "lastCleaned"
static let shared: PersistentContainer = {
ValueTransformer.setValueTransformer(ColorTransformer(), forName: NSValueTransformerName(rawValue: String(describing: ColorTransformer.self)))
let container = PersistentContainer(name: "ColorFeed")
container.loadPersistentStores { (desc, error) in
if let error = error {
fatalError("Unresolved error \(error)")
}
print("Successfully loaded persistent store at: \(desc.url?.description ?? "nil")")
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyStoreTrumpMergePolicyType)
return container
}()
var lastCleaned: Date? {
get {
return UserDefaults.standard.object(forKey: PersistentContainer.lastCleanedKey) as? Date
}
set {
UserDefaults.standard.set(newValue, forKey: PersistentContainer.lastCleanedKey)
}
}
override func newBackgroundContext() -> NSManagedObjectContext {
let backgroundContext = super.newBackgroundContext()
backgroundContext.automaticallyMergesChangesFromParent = true
backgroundContext.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyStoreTrumpMergePolicyType)
return backgroundContext
}
}
Personally I prefer passing the NSPersistentContainer around via dependency injection but it requires a lot more effort.