Are Today Extension Ivars Reset Constantly? - ios

I'm writing code to a new iOS 8 Today widget, but I noticed that each time that widgetPerformUpdateWithCompletionHandler: is called my ivars (created from #property) are reset. It's is like every time a new view controller is getting instantiated.
This makes it impossible to save data on memory between updates to the widget (while it is in the background, for example, and is called to update its content).
Is this normal behaviour, or a bug? Should I save my simple numbers to NSUserDefaults instead of relying on memory based data, which is being reset?

Your extension will not be running in between calls to widgetPerformUpdateWithCompletionHandler:. That method is called when iOS launches your extension in the background for you to fetch new data. The OS then captures an image of your extension (thats what the completion handler is for) to show as a sort of "launch screen" for your extension (when notification center is launched your extension isn't available immediately so it shows the image until it is). You likely want to use NSUserDefaults (or another method) to store cached data to load while waiting for updated data to come from a server.
In other words, the OS will launch your app periodically to let you fetch new data so that the user will always see updated data in notification center. You should cache this data in that method so that you can load your extension faster when it is launched for notification center. This is all discussed here.

Related

save state when phone turned off

I created a saveState() method that uses UserDefaults to save certain settings at certain parts of my App. It works fine when I exit the App and return, but if I actually turn my (iOS) phone off, when I start the App again, the settings are not saved. In addition to those places where I call the saveState() method in the code, I also call saveState() in three AppDelegate functions: applicationWillResignActive, applicationDidEnterBackground and applicationWillTerminate. I have a loadState() function in viewDidLoad so any saved information will load at that time. Does anyone know what I am not doing re: saving/restoring settings when phone powered off?
When iphone off, AppDelegate's applicationWillTerminate
function Called when the application is about to terminate
how about use synchronize() after your saveState() called
In apple API https://developer.apple.com/reference/foundation/userdefaults/1414005-synchronize
Writes any modifications to the persistent domains to disk and updates all unmodified persistent domains to what is on disk.

iOS - How long does NSManagedObject stay allocated without saving

Here is the scenario in my app: I download data from a JSON File which I stock in Coredata WITHOUT saving it. If the users wants to keep the data, he clicks on a button, and I save the context.
My question is: if the user doesn't click on the button and I don't save the data, how long will the Context stay the way it is? Until the user closes the app? Or even goes to background?
I'm looking for the best way to manage it.
Assuming that you do nothing to change it and that the app receives no memory warnings, doesn't crash and doesn't go to the background - indefinitely. If the app goes to the background it may be killed at any time if the OS requires it, so you can rely on nothing.
Really you should save the context as soon as possible. If you need to, save to a different store file on disk, then if the user discards you can delete that file and if they save you can move it to replace the original file (or just update a config which says where the current valid file is located on disk).

How can I update my iOS Today widget from the containing app?

I'm including a Today extension with the next release of my iOS app. The content of the widget updates only when the user makes a specific change to the database managed by the containing app.
Is there a way to send some kind of signal from the containing app to the Today widget process to let it know that its data has been invalidated and that it should reload itself the next time the user pulls down the Notification Center?
You don't need to update the widget yourself, iOS tries to update it periodically. Every time iOS does that, a function in your widget gets called. This is the function:
func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!)
If your data has changed, then call
completionHandler(.NewData)
else, if your data hasn't changed, call
completionHandler(.NoData)
That's it! And don't make anything inside that function that needs a lot of time, because iOS may "kill" your widget then.

NSUserDefaultsDidChangeNotification and Today Extensions

I am developing an iPhone app with a Today Extension. The app has a Model module that loads from/saves toNSUserDefaults. Since I want this information to be available to both the main app and the extension, I use an app group:
let storage = NSUserDefaults(suiteName: "group.etc.etc.etc...")
Both the app and the extension can access the information without any problem.
The main app occasionally might create a local notification to present to the user. That notification has two actions associated with it (UIUserNotificationAction). One of those actions triggers some code run on the background on the main app. That code changes the NSUserDefaults information and triggers a synchronization. My code is something like this:
func application(application: UIApplication, handleActionWithIdentifier id: String?, forLocalNotification not: UILocalNotification, completionHandler: () -> ()) {
// Interact with model here
// New information gets saved to NSUserDefaults
userDefaultsStorage.synchronize()
completionHandler()
}
Now, on the Today Ext. I naturally observe any changes made to the information on NSUserDefaults so that I can reload the interface on the widget:
override func viewDidLoad() {
super.viewDidLoad()
// ...
NSNotificationCenter.defaultCenter().addObserverForName(NSUserDefaultsDidChangeNotification, object: nil, queue: NSOperationQueue.mainQueue()) { _ in
self.reload()
}
}
Now, here's my issue:
The main app schedules a UILocalNotification. I open the today view and look at my today widget.
When the notification fires, a banner appears on the top of the screen.
I slide down on that banner to reveal the two actions and I select the one that I mentioned earlier (the today widget is still live and on screen).
I know for a fact that the action runs correctly in the background, and that the changes are being made to the information on NSUserDefaults.
However, even though the today widget has been active and on screen all this time, no reload action is triggered. After further investigation, I can confirm that the NSUserDefaultsDidChangeNotification is not being fired (I placed a breakpoint and it did not trigger, and did some other checks as well).
I know the changes are being made by the notification action because if I force a reload of the widget (by closing and opening the today view) the widget updates correctly.
I have seen various tutorials online where the first thing they say is to listen to this notification and update the widget so that "the widget is in sync with NSUserDefaults". But the thing is that AFAICT this notification is absolutely useless! How come??
Note 1: When I change the information on NSUserDefaults from within the today widget the notification fires correctly.
Note 2: Debugging a today widget is absolutely horrible, btw. It is always necessary to tell Xcode to "Attach to process by name..." before it can react to breakpoints and crashes. And iOS is constantly creating a new process for the widget so I have to constantly tell Xcode to attach again.
From doc here:
Cocoa includes two types of notification centers:
The NSNotificationCenter class manages notifications within a single
process. The NSDistributedNotificationCenter class manages
notifications across multiple processes on a single computer.
Apparently the containing app and today extension are different processes, since when you debug today extension you want to attach containing app process, but NSNotificationCenter only work within a single process.
In order to communicate between containing app and extensions, you can use
Darwin Notify Center CFNotificationCenterthat works like NSDistributedNotificationCenter, which is only available for osx.
The idea is use a file inside the group folder that they share. in containing app, you write the data you want to send into the file, then post a CFNotification, which will be received by today extension.
In today extension, use CFNotificationCenterAddObserver to observer the CFNotification, upon receiving it, callback will be called, in which a NSNotification has to be posted due to callback is a C style function and "userInfo" cannot be passed in the CFNotification, after receiving this NSNotification object, it starts to read data from the file, which is used to update the today extension view in Notification center.
You can use this github code to implement force loading the today extension view. It works for me.
Here is a great post on this. http://www.atomicbird.com/blog/sharing-with-app-extensions
Another option is to use setHasContent function. When you schedule a local identifier, set has content to false to hide the view, in handleActionWithIdentifier set it to true to show the view. This way, when you stay in notification center, you will not see the view for a moment, but when you see it, it will be the updated data.
let widgetController = NCWidgetController.widgetController()
widgetController.setHasContent(false, forWidgetWithBundleIdentifier: "YourTodayWidgetBundleIdentifier")
But I think the whole problem is a rare case, which doesn't need to be fixed since you can get the updated data reloading the notification center or switch to notification tab and switch back to today tab.

iCloud + Core Data: First import and user's feeling of loss of data

I've implemented an iPhone application that has around 50k users. Switching from iOS7 to iOS8 a lot of these users have experienced a terrible feeling when they thought that they data get lost.
I've implemented the first-import behaviour that I thought was the one suggested by Apple
1) Users launch the App
2) iCloud, automatically, starts synching data previously stored on iCloud
3) At some point user get notified that data from iCloud is ready thanks to NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted
The problem is with 3) At some point:
Users that have to sync a lot of data need minutes to get the synch completed and in the meanwhile they think that their data is lost.
I really don't know how to let my users know that they have to wait to see their data synched, because I don't know when this operation starts.
I'm thinking about a possible solution:
During the first launch of the App, asking to the user if he wants to use iCloud. If he chooses to use it, building the database with iCloud options, so I know exactly that the synch is starting here (I suppose...)
I'm really not sure about how to implement this behaviour since I've always seen Core Data settings into the AppDelegate but to achieve this behaviour I suppose I need to move all the CoreData settings in a Controller.
What do you think about this solution? how are you working around this problem in you Apps?
Your idea is right, at least it is that what we do. But leave it in the appDelegate.
Differentiate between with iCloud and without iCloud when doing the "addPersistentStoreWithType". If you do it with iCloud options, it will directly start to build the local store which is a kind of a placeholder ( I'm sure you know that, but just to make my thoughts clear). As soon as this is done, the sync starts from iCloud. So this is the starting point I understood you were looking for.
You can watch that process using the notifications by NSPersistentStoreCoordinatorStoresDidChangeNotification and inform you user accordingly triggered by that notification.
If you look at "Reacting to iCloud Events" in the docs https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/UsingSQLiteStoragewithiCloud/UsingSQLiteStoragewithiCloud.html#//apple_ref/doc/uid/TP40013491-CH3-SW5 there is a detailed desc.
To summarize, the event you're describing is part of the account transitions process. An account transition occurs when one of the following four events is triggered:
Initial import
the iCloud account used did change
iCloud is disabled
your application's data is deleted
During this event, Core Data will post the NSPersistentStoreCoordinatorStoresWillChangeNotification and NSPersistentStoreCoordinatorStoresDidChangeNotification notifications to let you know that an account transition is happening. The transition type we're interested in is NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted.
For information, I've moved all Core Data related code to my own Manager for simplicity and use it with a singleton design pattern. While setting up the singleton, I register the Manager for all relevant notifications (NSPersistentStoreDidImportUbiquitousContentChangesNotification, NSPersistentStoreCoordinatorStoresWillChangeNotification, NSPersistentStoreCoordinatorStoresDidChangeNotification, NSPersistentStoreCoordinatorWillRemoveStoreNotification).
I store several informations in my settings (NSUserDefaults or anything) like the last iCloud state (enabled, disabled, unknown), if the initial import is done or not, etc.
What I end up doing was having a prompt (UIAlertController or anything) to get a confirmation if the user wants to use iCloud or not. I have a displayICloudDialogAndForce:completion: method to do that and only do that if my iCloud state setting is unknown or I use the force parameter.
Then, after the user input, I call a setupCoreDataWithICloud: method, the iCloud boolean parameter depending on the user choice. I would then setup my Core Data stack, on the cloud or not according to the iCloud parameter.
If I'm setting up using iCloud, I would check my settings for the value of an iCloud imported key (boolean). If the value is NO, then I'm presenting a new modal to warn the user about the incoming import that could take some time.
I've registered my manager for different notifications and specially NSPersistentStoreCoordinatorStoresDidChangeNotification. In my storeDidChange: callback, I'm checking the transition type and if it's NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted, I'm changing the content of my modal to show the user that the import was successful and removing it a few seconds later, saving in my settings that the initial import is done.
- (void)storeDidChange:(NSNotification *)notification
{
NSPersistentStoreUbiquitousTransitionType transitionType = [notification.userInfo[NSPersistentStoreUbiquitousTransitionTypeKey] integerValue];
if (transitionType == NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted) {
[settings setDefaults:#(YES) forKey:kSettingsICloudImportedKey];
[ICloudModal dismissWithSuccess];
// ...
}
// Do other relevant things...
}

Resources