NSUbiquityIdentityDidChangeNotification doesn't work? - ios

I'm creating a cloudkit app, and have been trying multiple ways to get the NSUbiquityIdentityDidChangeNotification, but I never am able to get this notification.
I've tried both of these code versions under the delegate didFinish and the viewDidLoad methods. And I tried calling it from another notification - UIApplicationDidBecomeActiveNotification. I also put import Foundation at top of files.
Here's the basic code I've tried:
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "handleIdentityChanged:",
name: NSUbiquityIdentityDidChangeNotification,
object: nil)
// And this one I tried too from another post here on SO:
var localeChangeObserver = NSNotificationCenter.defaultCenter().addObserverForName(NSUbiquityIdentityDidChangeNotification, object: nil, queue: NSOperationQueue.mainQueue()) { _ in
println("The user’s iCloud login changed: should refresh all user data.")
}
Does anyone know how to get this notification to work for only a cloudkit app in swift? I really just want to detect the iCloud status change and then initiate fetching the userID if there's been a change.
Not that I need to access the ubiquityIdentityToken, but I was wondering why not store the token and every-time the app starts compare the current token with the one in local storage to see if it's a different account or nil? Therefore, why is getting the notification necessary?
Also, the code for getting the token only seems to work if I turn on "iCloud Documents", which I don't need. Does anyone know the implications of having that turned on for a social app that doesn't need it? And is there another way to get the token without enabling iCloud Documents?
This is the code I used to get token and placed in the delegate didFinish method, but only works if iCloud documents is turned on:
var token = NSFileManager.defaultManager().ubiquityIdentityToken
println("token is \(token!)")

On iOS, when I sign out of iCloud, my app is killed. So there seems not really to be a need to receive a NSUbiquityIdentityDidChangeNotification. Like you have said, it seems to be sufficient to compare the current token to the saved token.
On the Apple TV though, my app was not killed when I logged out of iCloud. Here I had noticed the notification was not fired, like you described. Since the app is not killed, a notification would be in order. (Did Apple forget to kill apps on Apple TV when iCloud account is changed?)
With Apple TV there is no iCloud documents container available (unless I explicitly share one from an iOS app). I found that on the dev center website, for the app identifier, iCloud was shown as "Configurable" and not "Enabled" if no document container was selected. I wonder if this has an effect on receiving notifications.
Both on the Apple TV and iOS, I can also confirm that the iCloud token is nil when not using documents (here: key-value-store only). Now that makes it difficult for Apple TV apps (because the app is not killed on iCloud account change, like on iOS) to detect account changes.
I have just noticed that my Apple TV app does received several NSUbiquitousKeyValueStoreDidChangeExternallyNotification when I log into another iCloud account, to reflect the changes. I guess this is as good as it gets. These notifications come with the NSUbiquitousKeyValueStoreChangeReasonKey key in userInfo, and a value of NSUbiquitousKeyValueStoreAccountChange indicates the account has changed.
Sorry for not being able to provide a direct solution, maybe it helped to share my experience.

To be notified in iOS when a user logs in or out of iCloud while using your app, use CKAccountChangedNotification instead of NSUbiquityIdentityChanged notification.
(Longer explanation: https://stackoverflow.com/a/38689094/54423.)

Related

Swift - register and re-register push notifications

Im trying to find a way to de-authorise users for push notifications when they log out from an app Ive found this function UIApplication.shared.unregisterForRemoteNotifications() which aparently works however I never see notifications being disabled, I also read in the documentation that it should not be used often or something to that effect, I basically want to have a toggle button in my app where the user clicks it one way and gets the standard enable notifications popup and another way to disable notifications on the fly, Im not a native swift developer so any pointers welcome
Also is it possible to attach a callback to this to know if it executes successfully, Im trying the following but get the error Argument passed to call that takes no arguments
UIApplication.shared.unregisterForRemoteNotifications() { (result, error) in
if let error = error {
call.error("Error", error)
} else if let result = result {
call.success([
"deregister": true
])
}
}
Edit: I found this which says its not possible to toggle on and off Change push notifications programmatically in Swift
With that in mind does this mean that the standard for devices is that:
1) when a user log out of their account they can still receive notifications.
2) When a user creates a new account on the same app it uses the same token and so receives notifications from the old account ?
3) when a user sells their phone and another guy/gal downloads the same app that they will receive notifications from that other users account (in terms of 3rd party push service one signal, aparently you dont need to refresh the player id)

Save/Delete to HealthKit With WatchKit and Widget (Today Extension)?

Here is a question that I know has an answer since I see apps that do this functionality. I have tried (writing directly, using background fetch) but nothing works. I found an app currently on the app store with the functionality that I am looking for. With Background Fetch set to OFF and main app NOT running in background. I go to the Widget and add an item. I open HealthKit and I see the data there as expected.
I would like to do the same for my app. I would like my today extension (widget) and/or WatchKit extension to write to the HealthKit store even when app is not running in background.
Like I said I have tested an app that does this functionality even though in Apple documentation it says this:
The HealthKit store can only be accessed by an authorized app. You
cannot access HealthKit from extensions (like the Today view) or from
a WatchKit app.
Because the HealthKit store is encrypted, your app cannot read data
from the store when the phone is locked. This means your app may not
be able to access the store when it is launched in the background.
However, apps can still write data to the store, even when the phone
is locked. The store temporarily caches the data and saves it to the
encrypted store as soon as the phone is unlocked.
Any answers or insights are appreciated. Thanks everybody.
The Health Data Store is indeed encrypted while the device is locked. Locked is defined as requiring a passcode on the device and the screen was turned off (so a passcode or touch id is required before you can get back to the main screen). While the store is encrypted it is not possible to read any data from it, no matter if the app is running in the background or not. Even setting up observer queries while the app is running will not allow it to continue to be read from. I imagine this level of protection is done simply using the Data Protection capability with the NSFileProtectionComplete option.
What HealthKit functionality have you observed in this other app? If it was displaying step and distance data, then they are likely getting this data directly from the pedometer (CMPedometer), which is not restricted when the device is locked.
Lehn0058's comment about authorization was correct. I had to request authorization explicitly from the WatchKit and Today Extension even though authorization was already given in the app. Afterwards both are able to write to the Health Store. The comment from Apple above only has to do with Reading from the Health Store and NOT writing to the Health Store. Here is some sample code for anybody else who gets in to the same problem. Thanks again.
In WatchKit InterfaceController.m
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Configure interface objects here.
[[HealthKitManager sharedManager] requestHealthKitAccess];
}
In Today Extension TodayViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[[HealthKitManager sharedManager] requestHealthKitAccess];
}

Check if user has AppleWatch connected without prompting the watch

We are using Google Analytics, and want to know how many of our users are in possession of an AppleWatch. I have searched Stack for answers, and the recurring answer is to use this:
if WCSession.isSupported() { // check if the device support to handle an Apple Watch
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession() // activate the session
if session.paired { // Check if the iPhone is paired with the Apple Watch
// Do stuff
}
}
The problem with this is that it prompts the user to 'accept' the app on the AppleWatch. Granted, the if-statement is true wether or not the user accepts, but I don't want the user to get their hopes up, thinking the app supports AppleWatch. I simply want to know if the user has an AppleWatch, I don't want to use it. Yet.
Is there a property on the iOS-device that can be accessed to show if the user has or ever had an AppleWatch connected, without prompting the user through the Watch?
Probably you can use (Push)Notifications, since there is no need to develop a native applewatch app to receive notifications on the watch. For example i receive "whats app messages" on my watch, but it does not have any native app on the watch either.
In your watchkit extension ExtensionDelegate.m you can handle provided answers to the push message separately. Link to Apple
This would be a(nother) approach, where you have to be creative!

NSUbiquityIdentityDidChangeNotification and SIGKILL

Swift 1.2
Xcode 6
Long-time listener, first-time caller.
Hello,
Straight from the horse's mouth: "To handle changes in iCloud availability, register to receive the NSUbiquityIdentityDidChangeNotification notification."
Here is the code they provide to implement this:
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: #selector (iCloudAccountAvailabilityChanged:)
name: NSUbiquityIdentityDidChangeNotification
object: nil];
I Swiftified it in my app to:
var observer = NSNotificationCenter.defaultCenter().addObserverForName
(NSUbiquityIdentityDidChangeNotification, object: nil, queue: NSOperationQueue.mainQueue()
){...completion block...}
src: https://developer.apple.com/library/ios/documentation/General/Conceptual/iCloudDesignGuide/Chapters/iCloudFundametals.html#//apple_ref/doc/uid/TP40012094-CH6-SW6
What is the correct way to implement this? Does it go in the AppDelegate? Do we remove the observer when the app gets sent to the background?
The problem I'm encountering is that when the Ubiquity Token changes, the app is terminated anyway because the user has changed iCloud settings.
How do you all manage to subscribe to this notification, and, if you don't, what do you do instead to keep track of the current logged in iCloud user?
Thank you!
Short Answer
To be notified in iOS when a user logs in or out of iCloud while using your app, use CKAccountChangedNotification, not NSUbiquityIdentityChanged.
Long Answer
I've been trying to get this to work as well. I remembered something from one of the talks at WWDC16 that there was something like this that they recommended to use. However, from the sample code they provide, I've only been able to find NSUbiquityKeyIdentityChanged, which I haven't been able to get to work.
So I went back to the video (it's from 2015). That's where I saw them refer to CKAccountChangedNotification – and it works exactly as expected:
Launch your app on the simulator from Xcode
Exit to the Settings app on the simulator
Log in (or out) of iCloud account
Go back into your app (tap icon on simulator home screen)
A notification is received.
Exit to Settings app again
Log back out (or in) to iCloud account
Go back into your app again
Another notification is received.
In Swift 3.0 there was another renaming:
Now the NSUbiquityIdentityDidChangeNotification has changed into NSNotification.Name.NSUbiquityIdentityDidChange.
So the full registering is the following:
// Register for iCloud availability changes
NotificationCenter.default.addObserver(self, selector: #selector(...), name: NSNotification.Name.NSUbiquityIdentityDidChange, object: nil)
On iOS 10 I found that NSUbiquityIdentityDidChangeNotification was never sent. Provided I had a CKContainer (as per the docs), CKAccountChangedNotification was sent in very limited circumstances.
Built with xCode 9.1 then tested on iOS 10.02 iPhone 6+ and iOS 11.0.3 iPhone SE
CKAccountChangedNotification was sent if
User logged into iCloud account, or
User enabled iCloud Drive in iOS 11. This always resulted in iCloud Drive->App being enabled. However, fetching the account status afterwards yielded NoAccount!
User enabled iCloud Drive in iOS 10. The subsequent state of iCloud Drive->App was whatever it was when I disabled iCloud Drive. The account status was appropriate. However, if iCloud Drive->App was disabled at this point, enabling it did not produce a termination or a notification.
Application was terminated if
User logged out of iCloud regardless of iCloud Drive status
User disabled iCloud Drive->App
User disabled iCloud Drive (even if iCloud Drive->App already disabled)
User started the app with iCloud Drive enabled, then enabled iCloud Drive->App
I found the same issues, see this question for my comments.
To summarize: On iOS I think apps are killed anyway when the iCloud account changes (or is just signed off). So no need to listen to NSUbiquityIdentityDidChangeNotification.
If you are on tvOS, however, your app is not killed. When your app becomes active, you do receive one or more NSUbiquitousKeyValueStoreDidChangeExternallyNotification with NSUbiquitousKeyValueStoreChangeReasonKey set to NSUbiquitousKeyValueStoreAccountChange because tvOS exchanges your entire ubiquity key-value-store.
Maybe you could use that with some trickery to detect account changes, e.g. store a NSUUID in the ubiquity key-value-store, and when the value changes, it means there is a new account. But you cannot detect if an account is logged off.

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