iCloud key-value sync iOS8 Xcode 6 - objective-c

Trying to fix some issue with iCloud. Here are two versions of my code.
- (void)viewDidLoad{
[super viewDidLoad];
[self checkICloudData];
}
version 1
- (void)checkICloudData
{
NSFileManager * fileManager=[NSFileManager defaultManager];
NSURL *iCloudURL=[fileManager URLForUbiquityContainerIdentifier:nil];
NSLog(#"iCloud URL is %#",[iCloudURL absoluteString]);
if (iCloudURL){
NSUbiquitousKeyValueStore * store=[NSUbiquitousKeyValueStore defaultStore];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateICloudData:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:store];
[store synchronize];
}else{
NSLog(#"iCloud is not supported or enabled");
[self loadDataFromBundle];
}
}
iCloudURL always returns nil. Other methods do not call.
version 2
- (void)checkICloudData
{
NSUbiquitousKeyValueStore * store=[NSUbiquitousKeyValueStore defaultStore];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateICloudData:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:store];
[store synchronize];
}
- (void)updateICloudData:(NSNotification*)notification
{
NSDictionary *userInfo = [notification userInfo];
NSNumber *changeReason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
NSInteger reason = -1;
if (!changeReason) {
return;
} else {
reason = [changeReason integerValue];
}
if ((reason == NSUbiquitousKeyValueStoreServerChange) || (reason == NSUbiquitousKeyValueStoreInitialSyncChange)) {
NSArray *changedKeys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
for (NSString *key in changedKeys) {
if ([key isEqualToString:casesKey]) {
[self iCloudNotification];
}else {
[self loadDataFromBundle];
}
}
}
}
With that version iCloud sync works fine with the iPad, but doesn't work with the iPhone. In the iPhone's iCloud Drive settings i see my app same as in the iPad
My iCloud settings:
Member center - iCloud Enabled. Compatibility - Include CloudKit support
Target Capabilities - Services checked only Key-Value storage
Created by default entitlements dictionary contains key com.apple.developer.icloud-container-identifiers with an empty array and key com.apple.developer.ubiquity-kvstore-identifier with a correct string value of my appID
So, why iCloudURL always returns nil? And why the second version works correct with the iPad but my iPhone does not see NSUbiquitousKeyValueStoreDidChangeExternallyNotification?

Well, the problem was in my iPhone after upgrade iOS.
When I've delete iCloud profile from iPhone's settings and add it again, the iPhone started to sync with iCloud.
Right now the second version of the code works with both devices.
Hope it helps somebody to solve that kind of problem.

Related

Connecting multiple keyboards in iOS 14 Objective-C

iOS 14 introduced GCKeyboard. I was able to successfully connect a BlueTooth NumberPad in my app but I need to be able to connect two of them in tandem. Could anyone please point me to sample code?
In the GCController class there is a "controllers" method which returns an array of all connected controllers, but there isn't anything similar for GCKeyboard.
Here is my code:
- (void)viewDidLoad {
[super viewDidLoad];
[[UIApplication sharedApplication]setIdleTimerDisabled:YES];
// notifications for keyboard (dis)connect
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(keyboardWasConnected:) name:GCKeyboardDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(keyboardWasDisconnected:) name:GCKeyboardDidDisconnectNotification object:nil];
}
GCKeyboard *leftKeyboard;
GCKeyboard *rightKeyboard;
- (void)keyboardWasConnected:(NSNotification *)notification {
if (!self.leftKeyboard) {
leftKeyboard = (GCKeyboard *)notification.object;
NSLog(#"Left Keyboard connected: %#\n", leftKeyboard.description);
NSString *keyboardStatus = [NSString stringWithFormat:#"Left Keyboard CONNECTED:\n%#", leftKeyboard.description];
self.statusLabel.text = keyboardStatus;
self.leftKeyboard = leftKeyboard;
[self reactToKeyboardInput];
}
else if (!self.rightKeyboard) {
NSLog(#"Right Keyboard connected: %#\n", rightKeyboard.description);
rightKeyboard = (GCKeyboard *)notification.object;
NSString *keyboardStatus = [NSString stringWithFormat:#"Right Keyboard CONNECTED:\n%#", rightKeyboard.description];
self.statusLabel.text = keyboardStatus;
self.rightKeyboard = rightKeyboard;
[self reactToKeyboardInput];
}
}
- (void)keyboardWasDisconnected:(NSNotification *)notification {
if (self.leftKeyboard) {
GCKeyboard *keyboard = (GCKeyboard *)notification.object;
NSString *status = [NSString stringWithFormat:#"Left Keyboard DISCONNECTED:\n%#", keyboard.description];
self.statusLabel.text = status;
self.leftKeyboard = nil;
}
else if (self.rightKeyboard) {
GCKeyboard *keyboard = (GCKeyboard *)notification.object;
NSString *status = [NSString stringWithFormat:#"Right Keyboard DISCONNECTED:\n%#", keyboard.description];
self.statusLabel.text = status;
self.rightKeyboard = nil;
}
}
When a keyboard or number pad is connected I get the message:
2020-09-18 13:09:15.845943-0700 Controller[1653:351628] Left Keyboard connected: GCController 0x280bb8dd0 ('Keyboard' - 0x27c3dc28cec4d818)
"Bring keyboard and mouse gaming to iPad" WWDC video transcript states:
In the case of keyboards, you can't differentiate multiple keyboards, and all keyboard events from multiple keyboards are instead coalesced for you. So while you might use notifications to notice when a keyboard disconnects and perhaps pause the game to prompt for different input, in general, you'll find that just using GCKeyboard-Device.coalesced to check on the key state when non-nil is the right path.
So while you may have multiple keyboards connected, it sounds like they all get coalesced into a single GCKeyboard instance and you can't differentiate between them. That is why there's no +[GCKeyboard keyboards] method. Instead there is +[GCKeyboard coalescedKeyboard]

iOS 9.2 - unable to see defaults registered from Settings.bundle in device Settings app

I have a project which uses Settings.bundle including root.plist containing a list of key value pairs I want to register with user defaults. Until recently, these values were visible and editable from the device's "Settings" App. Now I can't see anything when tapping on my app in settings - the details panel is empty.
How can I make sure my key-value pairs from the Settings.bundle provided with app properly display in the device's settings app?
Edit: It seems that restarting the settings app fixes the issue, but the details pane goes blank again if I redeploy the app from Xcode. Is it something with the new version of iOS that I'm not aware of?
Here's my code to register defaults:
- (void)registerDefaultsFromSettingsBundle {
NSString *settingsBundle = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"bundle"];
if(!settingsBundle) {
DLog(#"Could not find Settings.bundle");
return;
}
NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[settingsBundle stringByAppendingPathComponent:#"Root.plist"]];
NSArray *preferences = [settings objectForKey:#"PreferenceSpecifiers"];
NSMutableDictionary *defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];
for(NSDictionary *prefSpecification in preferences) {
NSString *key = [prefSpecification objectForKey:#"Key"];
if(key && [[prefSpecification allKeys] containsObject:#"DefaultValue"]) {
id object = [prefSpecification objectForKey:#"DefaultValue"];
if(object != nil)
{
[defaultsToRegister setObject:object forKey:key];
}
}
}
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsToRegister];
}
This is an Apple bug in iOS 9.2 and Simulator 9.2

can't load Safari contentBlocker. because can't access app group's NSUserDefault

I am making iOS 9 Safari AdBlocker app.
I am using the module of AdBlockPlusSafari.
App works good on simulator.
But when try to run it on device(iPhone6), it fails to reload contentBlocker.
[SFContentBlockerManager
reloadContentBlockerWithIdentifier:self.contentBlockerIdentifier
completionHandler:^(NSError *error) {
if (error) {
NSLog(#"Error in reloadContentBlocker: %#", error);
}
dispatch_async(dispatch_get_main_queue(), ^{
wSelf.reloading = NO;
[wSelf checkActivatedFlag];
if (completion) {
completion(error);
}
});
}];
it gives error
Error Domain=ContentBlockerErrorDomain Code=3 "(null)"
It caused by accessing the values in NSUserDefault (App Group).
- (instancetype)init
{
if (self = [super init])
{
_bundleName = [[[[[NSBundle mainBundle] bundleIdentifier] componentsSeparatedByString:#"."] subarrayWithRange:NSMakeRange(0, 2)] componentsJoinedByString:#"."];
NSString *group = [NSString stringWithFormat:#"group.%#.%#", _bundleName, #"AdBlockerPro"];
NSLog(#"Group name: %#", group);
_adblockProDetails = [[NSUserDefaults alloc] initWithSuiteName:group];
[_adblockProDetails registerDefaults:
#{ AdblockProActivated: #NO,
AdblockProEnabled: #YES
}];
_enabled = [_adblockProDetails boolForKey:AdblockProEnabled];
_activated = [_adblockProDetails boolForKey:AdblockProActivated];
}
return self;
}
The App Group name in host app and safari extension is same.
But in Safari extension, when app accesses the setting in NSUserDefault, it gives me the error.
In Project setting/Capabilities, I did all for App Group. In app id, it involves app group name exactly.
This happens on only device. On simulator, it works good. I can't find the reason of this error.
Please help me if you are experienced this.
Looking forward to your help.
I found the reason myself.
I have put something (NSMutableDictionary) in app group container and did something to write file to extension bundle.
It is prohibited by Apple.
So I deleted all from AdBlockManager (interacts with app group) except flag variables (Boolean type).
And I proceeded the file management using NSFileManager.
http://www.atomicbird.com/blog/sharing-with-app-extensions
Finally, app extension is working for me on device.
Good luck!

Tracking upload progress of iCloud files

I'm writing a Unity plugin which makes use of iCloud.
Now the way I currently use to move files to iCloud is using this code:
[byteDocument saveToURL:savePathURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success)
{
if (ubiquitousContainerURL != nil)
{
NSURL *ubiquityDocumentDirectory = [ubiquitousContainerURL
URLByAppendingPathComponent:#"Documents"
isDirectory:YES];
ubiquityDocumentDirectory = [ubiquityDocumentDirectory URLByAppendingPathComponent:[NSString stringWithFormat:#"%#%#", filename, extension]];
NSFileManager* fm = [NSFileManager defaultManager];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError* error = nil;
if (![fm fileExistsAtPath:ubiquityDocumentDirectory.path])
{
NSLog(#"Attemping to move to cloud");
didMoveToCloud = [fm setUbiquitous:YES itemAtURL:savePathURL destinationURL:ubiquityDocumentDirectory error:nil];
}
else
{
NSLog(#"Attemping to replace in cloud");
didMoveToCloud = [fm replaceItemAtURL:ubiquityDocumentDirectory withItemAtURL:savePathURL backupItemName:nil options:0 resultingItemURL:nil error:&error];
}
if (didMoveToCloud)
{
NSLog(#"Did move text document successfully to cloud");
//UnitySendMessage("GameObject", "DidSaveFile", [savePathURL.path cStringUsingEncoding:NSASCIIStringEncoding]);
}
else
{
NSLog(#"Failed to move text document to cloud");
//UnitySendMessage("GameObject", "DidSaveFile", [savePathURL.path cStringUsingEncoding:NSASCIIStringEncoding]);
}
if (error != nil)
{
NSLog(#"Error saving text document to cloud: %#", error.description);
//UnitySendMessage("GameObject", "DidSaveFile", [savePathURL.path cStringUsingEncoding:NSASCIIStringEncoding]);
}
});
}
}];
Basically I'm just using a simple inherited class of UIDocument called CloudByteDocument. I first save it locally on the device and then move it to iCloud afterwards.
Now the problem is that using SetUbiquituos does not seem to provide any way of tracking the upload progress? I'm already attached to both MSMetaDataQuery observerser, that is:
// Listen for the second phase live-updating
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(queryDidReceiveNotification:) name:NSMetadataQueryDidUpdateNotification object:nil];
// Listen for the first phase gathering
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(queryIsDoneGathering:) name:NSMetadataQueryDidFinishGatheringNotification
object:nil];
And I also realize that you can get things like upload and download progress during the NSMetadataQueryDidUpdateNotification, however this update method doesn't seem to provide very reliable results all the time.
I was wondering if there is any way in which you can use an actual callback method to notify you when a file has been uploaded to iCloud? Or is the NSMetadataQueryDidUpdateNotification method the standard way of doing so?
Any hints and help would be appriciated :)
Thank you for your time :)

Using MKReverseGeocoder in a singleton class (ARC)

I'm in the process of trying to create a singleton class for weather functions (all encompassed) so that I can change/update/recall weather data throughout my entire app in a single allocated object.
I have it working except for one little weird bug. I'm using MKReverseGeocoder on devices running iOS < 5. CLGeocoder works fine on iOS 5+. Basically, this is what happens in the app:
In the app delegate, at launch, I create the shared instance of my weather singleton. This instance doesn't do anything unless there is a UIView that expects to report weather results. When a view that reports weather is loaded, if the location isn't already statically set in the preferences, the app attempts to pull your current location.
This works, and I can log the coordinates of the devices current location. After that is done, I immediately go into trying to reverse geolocate those coordinates. The method MKReverseGeocoder start does get executed and I can log that the instance's isQuerying property is true, so I know it's attempting to geolocate the coords. (Yes, I set the delegate as my shared instance and the instance is of type MKReverseGeocoderDelegate).
Now this is the weird part. If I am launching the app for the first time, and I add a weather UIView to the screen for the first time, MKReverseGeocoder starts but never calls the delegate method. If I then close the app and open it again (second time), the current location get's looked up, and MKReverseGeocoder does call the delegate methods and everything works. It just doesn't want to work on the first launch, no matter how many times I call it (I have a button that can initiate the lookup).
It's totally baffling. The iOS 5 CLGeocoder works fine on initial launch and every subsequent launch. the MKReverseGeocoder does not work (because it doesn't call the delegate methods) on initial launch, but does on subsequent launches.
Below is the relevant code:
-(void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error{
NSLog(#"error getting location:%#", error);
self.reverseGeocoder = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:#"errorGettingLocation" object:nil userInfo:[NSDictionary dictionaryWithObject:error forKey:#"error"]];
}
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)pm
{
//update placemark and get weather from correct placemark
placemark = pm;
NSLog(#"singleton placemark: %#",placemark);
[self getLocationFromPlacemark];
self.reverseGeocoder = nil;
}
- (void) getReverseGeoCode:(CLLocation *)newLocation
{
NSLog(#"reversGeocode starting for location: %#", newLocation);
NSString *ver = [[UIDevice currentDevice] systemVersion];
float ver_float = [ver floatValue];
if (ver_float < 5.0) {
//reverseGeocoder = nil;
self.reverseGeocoder = [[MKReverseGeocoder alloc] initWithCoordinate:newLocation.coordinate];
self.reverseGeocoder.delegate = self;
[self.reverseGeocoder start];
if(self.reverseGeocoder.isQuerying){
NSLog(#"self.reverseGeocoder querying");
NSLog(#"self.reverseGeocoder delegate %#", self.reverseGeocoder.delegate);
NSLog(#"self %#", self);
}else {
NSLog(#"geocoder not querying");
}
}
else {
[reverseGeocoder5 reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks, NSError *error){
if([placemarks count]>0){
placemark = [placemarks objectAtIndex:0];
[self getLocationFromPlacemark];
}
else{
if (error) {
NSLog(#"error reverseGeocode: %#",[error localizedDescription]);
}
}
}];
}
}
Also, I am setting reverserGeocoder as a (nonatomic, strong) property and synthesizing it. Remember, this works fine after a clean launch the second time (when there is a weather UIView already loaded). The calls to log aren't even getting hit (which is why I'm assuming the delegate methods aren't getting hit).
Any input would be GREATLY appreciated!
Thanks!
I ended up figuring this out... well, kind of.
Instead of using MKReverseGeocoder, I'm just doing a static lookup to google maps ala How to deal with MKReverseGeocoder / PBHTTPStatusCode=503 errors in iOS 4.3?
Works perfectly.
MKReverseGeocoder doesn't exist anymore... in iOS 5
You have to import CoreLocation and use CLGeocoder. Below is just some example sample code :
CLGeocoder *geoCoder = [[CLGeocoder alloc] init];
CLLocation *location = [[CLLocation alloc] initWithLatitude:37.33188 longitude:-122.029497];
[geoCoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemark, NSError *error) {
CLPlacemark *place = [placemark objectAtIndex:0];
if (error) {
NSLog(#"fail %#", error);
} else {
NSLog(#"return %#", place.addressDictionary);
}
}];