Call master app's function with NC widget? - ios

how can I have my notification center today widget be able to call a function within my master app? I am trying to make an application that tracks when the device has activity by calling a function within the master app that logs an activity time in the core data.
code for logging the time and date
func insertNewObject(sender: AnyObject) {
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as! NSManagedObject
// If appropriate, configure the new managed object.
// Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
newManagedObject.setValue(NSDate(), forKey: "timeStamp")
// Save the context.
var error: NSError? = nil
if !context.save(&error) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
//println("Unresolved error \(error), \(error.userInfo)")
abort()
}
}
I'm willing to put any code in the NC widget for this to work
EDIT- Actually I figured out that all I need to do is share a xcdataobject between the two.

If you want to be able to share code between your NC widget and master application, you need to put that code in a framework. The framework will then need to be added to both the master application and the NC widget.

Related

Core Data sometimes loses data

We are running an App through Citrix Secure Hub, it seems that sometimes there is a rollback with loosing some Data in CoreData.
As i understand, CoreData is having something like an working copy of all the objects, and sometimes its tries to persist that on the filesystem.
Well tried to simulate the behavior but without any success, we could not find out any data loss or rollbacked data in our test environment.
So is there a way to force iOS to write the current "working copy" on the disk to prevent any data loss when using too much memory (and maybe crash)? We call our save function after
As we already found out:
We were NOT using:
func applicationWillResignActive(_ application: UIApplication) {
print("applicationWillResignActive")
}
to save the context, could this be a problem (we are already saving the context after every created object) ?
At the Moment we dont really handle problems when the context could not be saved, are there any recommendations how to handle that in a productive environment? And is it a good thing to maybe crash to app to prevent the user from struggeling with data loss?
Edit: this is the used Core Data Handler:
import Foundation
import CoreData
let context = CoreDataManager.shared.managedObjectContext
func saveContext(_ completion: (() -> Void)? = nil) {
CoreDataManager.shared.save(completion)
}
func saveContextSync() {
CoreDataManager.shared.saveSync()
}
class CoreDataManager: NSObject {
static let shared = CoreDataManager()
lazy var managedObjectContext: NSManagedObjectContext = {
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator
return managedObjectContext
}()
And our save functionality:
#objc func save(_ completion: (() -> Void)?) {
saveAsync(completion)
}
func saveAsync(_ completion: (() -> Void)?) {
func save() {
context.perform {
do { try context.save() }
catch {
// HERE WE NEED TO HANDLE IT FOR A PRODUCTIVE ENVIRONMENT
}
completion?()
}
}
if Thread.isMainThread {
save()
} else {
DispatchQueue.main.async {
save()
}
}
}
func saveSync() {
func save() {
context.performAndWait {
do { try context.save() }
catch { print(error)
// TRY TO REPRODUCE MEMORY LOSS APP TO SEE WHAT HAPPENS
abort()
}
}
}
if Thread.isMainThread {
save()
} else {
DispatchQueue.main.sync {
save()
}
}
}
Edit 2: This question in Objective C should be very similar:
Core Data reverts to previous state without apparent reason
Edit 3: It seems that there is no crash, some users telling me that they are adding data, then just press the home button and after a couple of hours the data from the last "task" is lost.
There are three possible causes.
Write Conflicts
Core data generally wants writes to be done in a single synchronous way. If you write in multiple ways at the same time to the same object (even if they are touching different properties and don't strictly conflict), it will be a merge conflict. You can set a merge policy (by default the value is 'error' - meaning don't apply the changes) but that is really a bad solution because you are tell core-data to lose information silently. see NSPersistentContainer concurrency for saving to core data for a setup to prevent merge conflicts.
Closing the app with unsaved data
If you setup your core-data correctly this shouldn't happen. The correct way to setup core data to only read from the 'viewContext' and write in a single synchronous way. Each writing is done in a single atomic block and the UI is only updated after it is saved. If you are displaying information from a context that is not saved to disk this can be a problem. For example it appears that your app only uses a single main-thread context for both reading and writing. Making changes to that context and not calling save will leave the app in a state where there are major changes that are only in memory and not on disk.
There is an error saving to disk
This is by far the rarest event, but there are users that have really really full disks. If this happens there is NOTHING that you can do to save. There is physically no room left on the disk. Generally the correct thing to do is to tell the user and leave it at that.
Without knowing more about your particular setup it hard to say for certain what your problem is. I would recommend the following setup:
use NSPersistentContainer
only read from the viewContext and never write to it.
make an operation queue for writing to core data
for every operation, create a context, make changes to it, and then save. Do not pass any managed object into or out of these blocks.
This should deal with all but the third problem.

unable to create a context in swift file

I need to fetch some data from CoreData and need to do it frequently and hence trying to create utility class for it.
When I try to create context for it, it gives me error and below is the code.
I added a new .swift file and pasted below code
import Foundation
import UIKit
import CoreData
class armyDataSource{
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
}
really not sure what wrong I'm doing it here.
if you want to create a wrappper class for core data manager you can write class like given below code in a swift file with your implementation.
import UIKit
import CoreData
class CoreDataManager {
static let sharedManager = CoreDataManager()
private init() {} // Prevent clients from creating another instance.
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "StackOF")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
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 {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
You cannot initialize these properties like that in a class. You need to do this initialization in a method, best in the init call.
Cannot use instance member 'appDelegate' within property initializer; property initializers run before 'self' is available
So this means that you cannot use a property to initialize another property because this is all done before the init is called and self is fully available.
Try this instead:
class armyDataSource {
let appDelegate: UIApplicationDelegate
let context: NSManagedObjectContext
init() {
appDelegate = UIApplication.shared.delegate as! AppDelegate
context = appDelegate.persistentContainer.viewContext
}
}

App Extension Programming Guide on sharing Core Data context with the main app

There is no documentation or sample code explaining if we can share the viewContext with the app extension or not.
AFAK, the app and the extension run in different processes and we should NOT share moc with another process/thread. I should not share the viewContext the containing app is using with the app extension.
So should we create another viewContext to use in app extension(? but NSPersistentContainer only provides one viewContext) or use a background context in app extension(???)
While an extension is running, it communicates directly only with the host app. There is no direct communication between a running extension and its containing app; typically, the containing app isn’t even running while its extension is running. In addition, the containing app and the host app don’t communicate at all.
So since they all run in different processes, so maybe (???) I can come to the conclusion that when the app extension ask for the viewContext, and when the containing app ask for the viewContext, the 2 viewContext(s) are actually distinct instances?
class Master {
static let shared: Master = Master()
lazy var persistentContainer: CCPersistentContainer = {
let container = CCPersistentContainer(name: "xt")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
private var _backgroundContext: NSManagedObjectContext!
var backgroundContext: NSManagedObjectContext {
if _backgroundContext == nil {
_backgroundContext = persistentContainer.newBackgroundContext()
}
return _backgroundContext
}
var viewContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
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)")
}
}
}
}
---- More On How To Sync Data Across Processes ----
1. WWDC 15: 224_hd_app_extension_best_practices
This WWDC session talks about how to post notification x processes.
2. NSMergePolicy
A policy object that you use to resolve conflicts between the persistent store and in-memory versions of managed objects.
3. WWDC 17: 210_hd_whats_new_in_core_data
4. UserDefaults(suiteName: AppGroups.primary)!
You can't share viewContext between the app and an extension. I don't mean you shouldn't, I mean it's actually, literally impossible even if you wanted to do so. An app and its extension are two separate processes. The viewContext object is created at run time by a process. There's no way for an app to hand off a variable in memory to a different process on iOS, so there's no possibility of using the same instance in both. You also have two different persistent container objects, again because they're created when the app or extension runs.
These two containers or view contexts might well use the same persistent store file. That's not unusual, and it allows the app and extension to access the same data.

Core Data Entity Unique Constraint Does Not Work

I am trying to set a constraint in core data with the new Entity Constraints inspector (To make the name of the item unique). All that I've read says it's pretty simple - Set the constraint and handle the error. I don't get any errors and can add the same entry as many times as I want.
The app does require IOS 9.0, Xcode tools requirement is set to 7.0
The constraint, category1Name, is a String.
My addItem code is:
func addNewRecord() {
//check to be sure the entry is not empty
if (categoryTextField.text == "") {
//prompt requiring a name
let ac = UIAlertController(title: nil, message: "Name Required", preferredStyle: .Alert)
ac.addAction(UIAlertAction(title: "Ok", style: .Default, handler: nil))
self.presentViewController(ac, animated: true, completion: nil)
} else {
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName("Category1", inManagedObjectContext: kAppDelegate.managedObjectContext) as! Category1
newManagedObject.category1Name = categoryTextField.text
newManagedObject.category1Description = categoryTextView.text
//bunch more items...
//save it
kAppDelegate.saveContext()
makeEntryFieldsEnabledNO()
performSegueWithIdentifier("unwindToCategoriesTableViewController", sender: self)
}//if else
}//addNewRecord
The AppDelegate save is standard:
func saveContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
//insert your standard error alert stuff here
let nserror = error as NSError
print("From the print line: Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}//do catch
}//if moc
}//saveContext
Here's the Core Data constraint:
This app is iCloud enabled.
The managedObjectContext merge policy is set to NSMergeByPropertyObjectTrumpMergePolicy
lazy var managedObjectContext: NSManagedObjectContext = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return managedObjectContext
}()//var managedObjectContext
Any guidance would be appreciated.
It would appear Apple have finally fixed the crazy Xcode problem where changes you make in a data model file don't actually change.
Putting that aside, the current formula seems to be:
in your core data singleton ...
container = NSPersistentContainer(name: _nom)
// during development, right HERE likely delete the sql database file
// and start fresh, as described here stackoverflow.com/a/60040554/294884
container.loadPersistentStores { storeDescription, error in
if let error = error {
print("\n ERROR LOADING STORES! \(error) \n")
}
else {
print("\n STORES LOADED! \(storeDescription) \n")
}
self.container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
self.container.viewContext.automaticallyMergesChangesFromParent = true
}
You must use merge policy and automatically merges.
Then in your data model file
Don't bother unless every relationship has an inverse, with
"to one or many" correctly set
and (almost certainly, except in very unusual source data) your unique id for each entity is indicated as a constraint
Then when you add new data, you must
use the new background context supplied by the handy core data function which does that
so, never try to make your own separate thread
double-check you have done (1) and (2) !
when you do add a few entities, you must do that inside a perform
and when you have finished adding entities (ie on the new thread) you must while still in the perform ...
do a performAndWait which does two things
save the new items (on the new child thread), and then
save the new items (on the main view thread)
naturally for both 7 and 8, you have to check .hasChanges before saving
Easy right?
So something like
let pm = core.container.newBackgroundContext()
pm.perform {
for onePerson in someNewData {
... create your new CDPerson entity ...
}
pm.bake()
}
Note that the bake routine is within the perform block,
and it looks like this:
func bake() {
self.performAndWait {
if self.hasChanges {
do {
try self.save()
}
catch {
print("bake disaster type 1 \(error)")
}
}
// OPTIONALLY, SEE BELOW
if core.container.viewContext.hasChanges {
do {
try core.container.viewContext.save()
}
catch {
print("bake disaster type 2 \(error)")
}
}
// OPTIONALLY, SEE BELOW
}
}
To be clear, notice the pm.bake ... in the function bake(), the self in the first half is indeed that newBackgroundContext which is created for the loop inside the perform.
Note that these days you don't even need to save to the main context
Nowadays automaticallyMergesChangesFromParent seems to work perfectly, if you "do everything in the long list above".
• In the bake above, add a couple print lines to see what is saved to the viewContext. You'll see that nothing, at all, is ever saved. It's all done properly by the child/whatever relationships in the engine
• So in fact, in reality you can just omit that passage of the code. All you have to do is
func bake() {
self.performAndWait {
if self.hasChanges {
do {
try self.save()
}
catch {
print("bake disaster type 1 \(error)")
}
}
}
If you want to get an error when there are merge conflicts and handle them manually then you need to change your policy to NSErrorMergePolicy and you will get an error and in the user info the object IDs that you need to solve merge conflict , otherwise it will merge and save according to the specified merge policy.
The policy that you set will overwrite the object attributes but not relationships, if you want to overwrite the attributes and relationships then specify the NSMergeByPropertyObjectTrumpMergePolicy.
The comment from pbasdf above seems to be correct. Constraints in the inspector don't save. I used both methods suggested in the link provided - I added the constraint, I changed another attribute, did a file save, then changed that attribute back and did a file save again. The constraint now acts as I expect. I'll mark this as answered. pbasdf should get the credit.

Implement CoreData into an existing project using Swift

I am currently using Xcode 6 beta 7 and decided I needed CoreData after already creating my project. I have looked at some of the questions already asking this for objective C along with watching videos on the subject but I seem to be getting the same error. As for my usage of the CoreData framework itself, I am pretty sure it is correct as I created another empty project (with the CoreData box checked) and followed the same implementation and it worked like a charm but there may be something wrong I'm doing there too. So here are the steps I followed to implement CoreData in my project in Swift.
Step 1: I added the CoreData framework through "Link Binary with Libraries" tab under "Build Phases"
Step 2: I then went to any of my .swift files which would need to implement CoreData (appDelegate.swift and one other file as of now) and added the line:
import CoreData
to the top.
Step 3: I created a data list with an entity called "cData", then made a class for it called "data.swift". Here is data.swift:
import UIKit
import CoreData
#objc( data )
class data: NSManagedObject {
#NSManaged var something : String
}
Step 4: I wrote the code to actually save the data in another file:
#IBAction func useCoreData(sender: AnyObject)
{
let AD : AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let ct : NSManagedObjectContext = AD.managedObjectContext!
let ent = NSEntityDescription.entityForName( "CData", inManagedObjectContext: ct )
var dat = data( entity: ent!, insertIntoManagedObjectContext: ct )
dat.something = someTextField.text
ct.save( nil )
println(dat)
}
Step 5: Here is where I believe I messed up, though I could be wrong. I created a new project on a separate Mac (so I could name it the same thing) and checked the CoreData box. I then copied the entire appDelegate.swift from this project to my current one. Here is my appDelegate.swift:
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
self.saveContext()
}
// MARK: - Core Data stack
lazy var applicationDocumentsDirectory: NSURL = {
// The directory the application uses to store the Core Data store file. This code uses a directory named "y.Simple_Grade" in the application's documents Application Support directory.
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1] as NSURL
}()
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = NSBundle.mainBundle().URLForResource("my_app", withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)
}()
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
// The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("my_app.sqlite")
var error: NSError? = nil
var failureReason = "There was an error creating or loading the application's saved data."
if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil, error: &error) == nil {
coordinator = nil
// Report any error we got.
let dict = NSMutableDictionary()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error
error = NSError.errorWithDomain("YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(error), \(error!.userInfo)")
abort()
}
return coordinator
}()
lazy var managedObjectContext: NSManagedObjectContext? = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
if coordinator == nil {
return nil
}
var managedObjectContext = NSManagedObjectContext()
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
// MARK: - Core Data Saving support
func saveContext () {
if let moc = self.managedObjectContext {
var error: NSError? = nil
if moc.hasChanges && !moc.save(&error) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(error), \(error!.userInfo)")
abort()
}
}
}
}
Once the IBAction method "useCoreData" is called, I get a crash and the exception states "fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)" and states the offending line is in appDelegate.swift:
let modelURL = NSBundle.mainBundle().URLForResource("my_app", withExtension: "momd")!
Again, when I try this same CoreData implementation in another project that had the CoreData box checked from the get-go, it works like a charm. I saw a similar question like this (the user was getting the same error), but it doesn't seem like his problem was solved either.
You're getting an nil value there because the file it's looking for doesn't exist in your main bundle.
You need to copy your data model file from the other project you created to your main project. The file would be called something like My_App.xcdatamodeld and should be located in the same folder that your Xcode project file is in.
Note: The URLForResource line is looking for My_App.momd; that file is created by Xcode from My_App.xcdatamodeld when it compiles your project.

Resources