NSUbiquitousKeyValueStore.didChangeExternallyNotification fires also when change is done on device itself - cloudkit

My app worked fine until iOS 16.0.
When I write to iCloud: NSUbiquitousKeyValueStore then the didChangeExternallyNotification notification is triggered also on the device I wrote to iCloud. So it did not change externally but internally. Therefore my app runs into a loop ;-(
Does anyone have the same issue?

I had contact with Apple and found at the you need to use 1 var:
let defaultsAppGroup = UserDefaults(suiteName:"xxx")
When using:
UserDefaults(suiteName:"xxx").set()
Multiple instances of UserDefault are made and therefore a didChangeExternallyNotification will be triggered to the other instances.

Related

What communication to use, updateApplicationContext or sendMessage for constant data sync

I have an iOS shopping list app, where items are added and displayed in a tableView. I want to create a Watch App Extension but I’m thinking of what is the best function call to use in this case, the updateApplicationContext(:) method or the sendMessage(:replyHandler:errorHandler:) method I was reading the documentation but I’m a little confused since both seem to work.
Here is the functionality I’m expecting to have…
What I want is to be able to add items in the iOS app even if the Watch app is Off, which is normal behavior, BUT I want the Watch app to update with whatever is in the tableView (in iOS) as soon as it is turned on and even if the iPhone is not On at the time the Watch is turned on.
In other words, I want the data in the iOS app to always be in sync with the Watch app.
Which is the best function call to use in this case, the updateApplicationContext(_:) method or the sendMessage(_:replyHandler:errorHandler:) method?
Thanks
As for me, I would use updateApplicationContext(_:) since you would want to update it in real time or in the background as it gets connected.
as for this sendMessage(_:replyHandler:errorHandler:) The cons is The isReachable property must currently be true for these methods to succeed. so you might get a slight delay to update your UI until it is reachable and ask for updates.

Swift UserDefault :sharing data via share group doesn't work

just started swifting recently and got an issue by using app group to share data between iOS devices.
basically I have setup the project followed the steps below:
[iPhone]
Enabled App Group for the iPhone target
initialed data below(groupID is matched what I set in the project):
sharedDefaults = NSUserDefaults.init(suiteName: groupID)
sharedDefaults?.setObject("User Default Shared String", forKey: "test")
sharedDefaults?.synchronize()
double checked the test string by loading user default locally, which is able to display in the Log.
let t_sharedDefaults = NSUserDefaults.init(suiteName: groupID);
t_sharedDefaults?.synchronize();
let str = t_sharedDefaults?.valueForKey("test") as! String;
print(str);
[watch extension]
Create a new watch extension target.
Enable App Group.
Load the User default data under awake with context via the code below:
sharedDefaults = NSUserDefaults.init(suiteName: groupID);
sharedDefaults?.synchronize();
let str = sharedDefaults?.valueForKey("test")
print(str);
I have run the iPhone target first, and then the watch app.
However, the watch app is not able to read expected data from the user default.
I have also uploaded the test project in Github, please let me know if you have any thought on this issue.
[Github]
https://github.com/mattcn/WatchOS_DataSharing
Thanks for your help in advance.
In Watch OS1 this worked, but in Watch OS2 there has been some changes. You need to use something called WatchConnectivity to send the data you want to save to the watch.
so even if you use shared user defaults or app groups the data you put inside them will be on the watch and so not accessible from the iOS app.
To send data to the watch you can use the WatchConnectivity framework.
Thanks Ashish to bring the difference between OS1 and OS2 up.
just found a good reference from:
NSUserDefaults(suiteName:) on iOS 9 and WatchOS 2 - not working?
In watchOS 2 you need to keep in mind that there is 2 different processes running:
Apple Watch Process
iPhone Process
Both of these processes have their own sandbox that's why they call it "native", so if you try to use the shared NSUserDefaults it will not work because the Apple watch app has a completely different sandbox than the host iPhone app.
If you want to save something from your phone to the NSUserDefaults on the Apple watch Target:
Use WatchConnectivity to send the data you want to save to the watch. Then when the watch receives the data you sent to it, save it to the Apple watch's default NSUserDefaults.

Get Data to Complication: ExtensionDelegate not Called

(It looks like this issue has been encountered by others in previous weeks, but there haven't been any solutions that I've found.)
I'm trying to do a really basic thing: Get data from either my iOS app or my Watch app to my Complication Controller.
I am turning out to be way less capable of getting this done than I thought. watchOS 2 Transition Guide indicates that I should "[fetch] the needed data from the extension delegate" using the following code:
ExtensionDelegate* myDelegate = [[WKExtension sharedExtension] delegate];
NSDictionary* data = [myDelegate.myComplicationData objectForKey:ComplicationCurrentEntry];
Great. Except, I haven't been able to figure out how to get this to work on the extension side. Though even more importantly, I can't seem to even get the extension delegate code to run at all from a complication controller launch. When I run the complication, I get this message: "Extension received request to wake up for complication support." However, none of the code within any of the extension delegate's methods seems to run. I've also set breakpoints within every method and none of those breakpoints are hit.
It also looks like "transferCurrentComplicationUserInfo:" is also suggested to be used for complication updates, though it's unclear precisely how it's used. As much as I've gathered, it's used to wake up the watch extension so that ExtensionDelegate can store the new data for the next time the complication controller runs, but due to the previous issue I haven't been able to confirm.
I've got one maybe workaround (pinging the server from the complication controller and hoping that session variables persist so I can send relevant data), but there's every chance that if I can't get this worked out my complication work will be hosed. Any help here would be tremendous.
By the way, here's the code I have for "getCurrentTimelineEntryForComplication", if that's helpful at all.
- (void)getCurrentTimelineEntryForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimelineEntry * __nullable))handler {
NSDate* entryDate = [NSDate date];
ExtensionDelegate* myDelegate = [[WKExtension sharedExtension] delegate];
NSString* data = [myDelegate.complicationData objectForKey:#"meow"];
NSLog(#"complication data: %#", data);
CLKComplicationTimelineEntry* entry = [self getTimelineEntry:#"2015-08-25 00:19:42" entryDate:entryDate complication:complication];
handler(entry);
}
I've been working with Complications in WatchOS2 since Xcode 7 Beta 4. I'm now on the latest, Xcode Beta 6. I've had a number of issues as in both Beta versions running on the Watch, running on the iPhone then installing to the Watch, and running on the simulator frequently give false negatives due to what appears to be buggy APIs and OS releases. I have been able to get data to show on complications in the following way.
Ensure that your primary Interface Controller implements the WCSessionDelegate protocol.
Implement both the didReceiveMessage and didReceiveApplicationContext methods in your Interface Controller.
In your iPhone app, attempt to send a message using the WCSession to the Watch.
If the message fails to send from the iPhone app, send the application context.
Back in the Interface Controller, when you receive a message -or- a context, update the values in your Extension Delegate.
Still in the Interface Controller and still after receiving a message -or- context, get a handle to the CLKComplicationServer and for each complication in activeComplications call reloadTimelineForComplication.
In your Complication Controller's getCurrentTimelineEntryForComplication grab the data you set in the Extension Delegate and set the values in your CLKComplicationTimelineEntry.
This will work usually when the App is already open on the Watch, the app is still resident in memory, but backgrounded on the Watch, or you start the app and their is context waiting which it consumes.
I have not been able to get the historical timeline entries to function (or the future ones). Nor, I have I been able to get the timeline to update independently of the Watch app.
If you are having trouble, here are some debugging things to try. As I stated above, the API and OS appears to be very buggy. The steps below do work (sometimes).
In the sim, use the Reset all Settings option on both the iPhone and Watch sim.
On the device, restart the Watch. If necessary, unpair and repair the Watch, although this takes a really long time to do.
On the iPhone, delete the app (which will also delete the Watch app if installed) and reinstall.
I hope that helps!
Justin
In order to make the ComplicationController respond to WCSession activity you must make the controller conform to WCSessionDelegate, then manage didReceiveUserInfo from within the ComplicationController. The ExtensionDelegate is not woken up for these updates when in the background. You can still update your delegate from the controller if necessary.
Also, as of right now, the simulator does not send transferCurrentComplicationUserInfo to the watch sim, you have to test on devices.

How to create a global environment variable that can be accessed by SpringBoard or other applications in the jailbroken iPhone?

I found that a variable created in SpringBoard can not be accessed by other regular applications. But now I want to make a flag variable that can share status in the global environment efficiently.
I thought a file created at some path could do that, but that may be not fast enough.
Does any body know how to do this?
You can try combination of a file with notifications of changes in this file. Notifications between processes can be sent in two ways:
Darwin notification center CFNotificationCenterGetDarwinNotifyCenter
Distributed notification center CFNotificationCenterGetDistributedCenter - private API
Distributed notification center is better because you can send notification with some data attached to it. Darwin notification center ignores all user info passed to it. So when you changed some flag and saved it in a file you can send notification with this flag's new value. You don't even need to open file and get flag's value yourself. All other apps just need to listen for this notification.
Here is CFNotificationCenterGetDistributedCenter prototype
CFNotificationCenterRef CFNotificationCenterGetDistributedCenter();
Update:
This function is available in iOS 5.0 and above. If you need to support older versions there are two solutions:
Darwin notification center
CFMessagePort - can transmit arbitary data.
If you really need to support older versions I suggest you using CFMessagePort. It's simple and flexible solution, well documented. If you having troubles with it you can always find working examples.
You can store your var in the keyChain, and make all app which wanna share it in one access group. Hope it help.

When does an iphone application receive didChangeAuthorizationStatus: delegate call?

I have a question about CLLocationManagerDelegate. The documentation says if the user changes the settings for your location services (in the iPhone's Settings.app) then your app is supposed to receive an didChangeAuthorizationStatus: message to the delegate. My question is, when would this happen?
If the user changed the setting, it means they are in the settings app, and your app is either backgrounded or not running at all, so in the former case, when would your app's CLLocationManager delegate get the didChangeAuthorizationStatus: call?
I just ran across this method an hour ago, so good timing on the question!
It looks like in my case this method gets called:
When the app becomes active.
On allowing Location Services for the app on the initial startup of the app.
I wrote a quick test app you can find here:
https://github.com/mharper/LocationServices
It simply logs the authorization status whenever the method gets called.
This delegate method will be called when:
The first time you init a CLLocationManager instance
1.1. If that's the first time your App launch on device, you'll receive state kCLAuthorizationStatusNotDetermined before user see the [Allow/Don't Allow] UIAlertView. (At this time, you can find that the UISwitch of your App in Settings - Privacy - Location Service is turn off or not shown.
1.2. When user re-launch your App. Because decision has been made in previous launch, so you can retrieve the state.
The first time after user made decision. This is obvious. After you call the startUpdatingLocation/startUpdatingHeading, then iOS show the UIAlertView.
In your answer's situation, user made changes in Settings, if your App is running in background, you'll receive the new state when your App become active. Otherwise, reference 1.2.
If your app is running in the background or not at all it will be called the moment the user returns to your application.
I encounter the same issue as well.
my solution is put the request gps authorization code in main loop.
I guess it can also works if you put in another run loop.
I ran into this problem of handling location permission changes correctly recently and did a lot of research and debugging.
If the app is not running at all and the user changes location permissions in Settings, then when you start your app, locationManager:didChangeAuthorizationStatus is called when the location manager is initialized as stated in mharper's answer. This behavior is not in Apple's docs.
If the app is in the background/suspended, I tested it in the simulator and it looks like the delegate function is also called.