How to check if iCloud is configured programmatically - iphone

Here is the sentence from Apple Docs:
"If iCloud is not configured, ask users if they want to configure it (and, preferably, transfer them to Launch Settings if they want to configure iCloud)."
How can I check if iCloud is configured or not and how to launch settings for iCloud?

Edit:
If you are targeting iOS6 or above you can use [[NSFileManager defaultManager] ubiquityIdentityToken];. For usage example please refer #Dj S' answer :).
It is faster and easier than the original solution which was meant for people targeting iOS5 and above
Original Answer
As documented in iOS App programming guide - iCloud Storage. That can be checked by asking the ubiquity container URL to the file manager :)
As long as you supply a valid ubiquity container identifier below method should return YES
- (BOOL) isICloudAvailable
{
// Make sure a correct Ubiquity Container Identifier is passed
NSURL *ubiquityURL = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:#"ABCDEFGHI0.com.acme.MyApp"];
return ubiquityURL ? YES : NO;
}
However, I've found that URLForUbiquityContainerIdentifier: might take several seconds the very first time within a session (I used it in iOS5 so things might be different now). I remember using something like this:
dispatch_queue_t backgroundQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue,^{
BOOL isAvailable = [self isICloudAvailable]
/* change to the main queue if you want to do something with the UI. For example: */
dispatch_async(dispatch_get_main_queue(),^{
if (!isAvailable){
/* inform the user */
UIAlertView *alert = [[UIAlertView alloc] init...]
[alert show];
[alert release];
}
});
});

Just to supplement the answer above,
if you only want to know if iCloud is available for your application,
e.g.
1. no iCloud account is setup, or
2. Documents and Data is disabled (for all apps), or
3. Documents and Data is disabled for your app only
then you can use NSFileManager's ubiquityIdentityToken for iOS 6 and above.
If value is nil, then iCloud account is not configured. Otherwise, iCloud account is configured.
id token = [[NSFileManager defaultManager] ubiquityIdentityToken];
if (token == nil)
{
// iCloud is not available for this app
}
else
{
// iCloud is available
}
Note that according to Apple docs, you can call it from the main thread.
Because this method returns relatively quickly, you can call it at launch time and you can call it from your app’s main thread.

Related

Google + iPhone API sign in and share without leaving app

I recently integrated the Google + API in my App, it was a breeze, my only problem with it, is that everything requires you to leave the app and then come back (it uses URL schemes for this). This is not the behavior I would like, is there a way to directly call their services and do whatever I want with the responses just like in LinkedIn API?.
I really want to avoid going back and forth between safari and my app. Any suggestions/documentation is appreciated.
Thank you,
Oscar
UPDATE FROM GOOGLE
today, we released a new Google Sign In iOS SDK with full built-in
support for Sign In via WebView:
developers.google.com/identity/sign-in/ios The SDK supports dispatch
to any of a number of Google apps handling Sign In when present, with
WebView fallback after. In all cases, the Safari switch is avoided,
which we've seen to be the key element in avoiding app rejection.
We're looking forward to getting feedback from people using the new
SDK, and hope its use can replace the (ingenious and diligent)
workarounds people have implemented in the meantime.
THE METHOD BELLOW IS NO LONGER NEEDED
THIS METHOD HANDLES THE LOGIN INTERNAL WITH A CUSTOM UIWebView
THIS WORKS AND WAS APPROVED BY APPLE
My app got kicked from review cause of this
"The app opens a web page in mobile Safari for logging in to Google plus,
then returns the user to the app. The user should be able log in without opening
Safari first."
See this link https://code.google.com/p/google-plus-platform/issues/detail?id=900
I did solved it by following steps
1) create a subclass of UIApplication, which overrides openURL:
.h
#import <UIKit/UIKit.h>
#define ApplicationOpenGoogleAuthNotification #"ApplicationOpenGoogleAuthNotification"
#interface Application : UIApplication
#end
.m
#import "Application.h"
#implementation Application
- (BOOL)openURL:(NSURL*)url {
if ([[url absoluteString] hasPrefix:#"googlechrome-x-callback:"]) {
return NO;
} else if ([[url absoluteString] hasPrefix:#"https://accounts.google.com/o/oauth2/auth"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:ApplicationOpenGoogleAuthNotification object:url];
return NO;
}
return [super openURL:url];
}
#end
this will basically prevent anything to be opened from Chrome on iOS
we catch the auth call and redirect it to our internal UIWebView
2) to info.plist, add the Principal class, and for it Application (or whatever you named the class)
Add plist key "NSPrincipalClass" and as the value the class of your main application (class which extends UIApplication, in this case Application (see code above))
3) catch the notification and open an internal webview
When your custom Application class sends ApplicationOpenGoogleAuthNotification, listen for it somewhere (in the AppDelegate maybe) and when you catch this notification, open a UIWebView (use the URL passed by the notification as the url for the webview) (in my case the LoginViewController listens for this notification and when received, it opens a view controller containing only a webview hooked up to delegate)
4) inside the webview
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if ([[[request URL] absoluteString] hasPrefix:#"com.XXX.XXX:/oauth2callback"]) {
[GPPURLHandler handleURL:url sourceApplication:#"com.google.chrome.ios"n annotation:nil];
// Looks like we did log in (onhand of the url), we are logged in, the Google APi handles the rest
[self.navigationController popViewControllerAnimated:YES];
return NO;
}
return YES;
}
or simmilar code, that handles the response
com.XXX.XXX:/oauth2callback from code above, replace with your company and app identifier, like "com.company.appname:/oauth2callback"
you might want to use #"com.apple.mobilesafari" as sourceApplication parameter
So, it depends what you want to do.
Sign-In: this will always call out to another application. If the Google+ application is installed it will call out to that, else it will fall back to Chrome and Safari.
Sharing/Interactive Posts: right now this always uses Chrome or Mobile Safari.
Retrieving friends, writing app activities, retrieving profile information: All this is done with the access token retrieved after sign in, so does not require leaving the application.
It is possible, though rather unsupported, to skip the SDK and pop up a UIWebView, construct the OAuth link dynamically and send the user to that (take a look at GTMOAuth2ViewControllerTouch in the open source libraries that ship with the SDK). Below is the a very rough example of the kind of thing you could do to plumb it back into the GPPSignIn instance.
However, you would be guaranteeing that the user has to enter their username and password (and maybe 2nd factor). With the Google+ app you're pretty much guaranteed to be already signed in, and with the Chrome/Safari route, there is a chance the user is already signed in (particularly if they're using other apps with Google+ Sign-In).
This also doesn't address sharing, so I would strongly recommend using the existing SDK as far as possible. Filing a feature request for the way you would prefer it to work would be a good thing to do as well: https://code.google.com/p/google-plus-platform/issues/list
#interface ViewController() {
GTMOAuth2ViewControllerTouch *controller;
}
#end;
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
GPPSignIn *signIn = [GPPSignIn sharedInstance];
signIn.clientID = #""; // YOUR CLIENT ID HERE.
signIn.delegate = self;
}
- (IBAction)didTapSignIn:(id)sender {
void (^handler)(id, id, id) =
^(GTMOAuth2ViewControllerTouch *viewController,
GTMOAuth2Authentication *auth,
NSError *error) {
[self dismissViewControllerAnimated:YES completion:^{
[controller release];
}];
if (error) {
NSLog(#"%#", error);
return;
} else {
BOOL signedIn = [[GPPSignIn sharedInstance] trySilentAuthentication];
if(!signedIn) {
NSLog(#"Sign In failed");
}
}
};
controller = [[GTMOAuth2ViewControllerTouch
controllerWithScope:kGTLAuthScopePlusLogin
clientID:[GPPSignIn sharedInstance].clientID
clientSecret:nil
keychainItemName:[GPPSignIn sharedInstance].keychainName
completionHandler:handler] retain];
[self presentViewController:controller animated:YES completion:nil];
}
- (void)finishedWithAuth:(GTMOAuth2Authentication *)auth
error:(NSError *)error {
if (!error) {
UIAlertView * al = [[UIAlertView alloc] initWithTitle:#"Authorised"
message:#"Authorised!"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[al show];
[al release];
}
}
The only real trick to this code is that it uses the [GPPSignIn sharedInstance].keychainName - this means that the auth tokens get stored in the same keychain entry as the GPPSignIn button would, which in turn means we can use [[GPPSignIn sharedInstance] trySilentAuthentication] once it has been populated, and keep the same callback based flow as the main library.
#PeterLapisu approach works good if the Google Plus App is not installed.
Then outgoing url prefixes from app are as follows:
#"googlechrome-x-callback:"
#"https://accounts.google.com/o/oauth2/auth"
However if the Google App is installed there is one more outgoing url and the prefix list looks as follows:
#"com.google.gppconsent.2.4.1:"
#"googlechrome-x-callback:"
#"https://accounts.google.com/o/oauth2/auth"
So, if the Google App is installed, it will be launched simultaneously with our app UIViewController that contains webview.Then if user sucessfully logs in with Google App he will be directed back to our app and the ViewController will be visible.
To prevent this Google App should be allowed to login user and direct him back to our app. According to this discussion: https://code.google.com/p/google-plus-platform/issues/detail?id=900 it is allowed by Apple.
So in my implementation firstly I am checking if the Google App is installed:
- (BOOL)openURL:(NSURL*)url {
NSURL *googlePlusURL = [[NSURL alloc] initWithString:#"gplus://plus.google.com/"];
BOOL hasGPPlusAppInstalled = [[UIApplication sharedApplication] canOpenURL:googlePlusURL];
if(!hasGPPlusAppInstalled)
{
if ([[url absoluteString] hasPrefix:#"googlechrome-x-callback:"]) {
return NO;
} else if ([[url absoluteString] hasPrefix:#"https://accounts.google.com/o/oauth2/auth"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:ApplicationOpenGoogleAuthNotification object:url];
return NO;
}
}
return [super openURL:url];
}
EDIT:
Now I can confirm that my app was finally approved with this solution.
I hope it will help somebody.
It merges Google+ and Gmail samples and completely avoids using Google SignIn Button, i.e you do not leave the app.
Add both Google+ and Gmail API to you Google project, in your app login to google as you would to gmail using GTMOAuth2ViewControllerTouch.xib from OAuth2 and set scope to Google+:
-(IBAction)dologin{
NSString *scope = kGTLAuthScopePlusLogin;//Google+ scope
GTMOAuth2Authentication * auth = [GTMOAuth2ViewControllerTouch
authForGoogleFromKeychainForName:kKeychainItemName
clientID:kClientID
clientSecret:kClientSecret];
if ([auth refreshToken] == nil) {
GTMOAuth2ViewControllerTouch *authController;
authController = [[GTMOAuth2ViewControllerTouch alloc]
initWithScope:scope
clientID:kClientID
clientSecret:kClientSecret
keychainItemName:kKeychainItemName
delegate:self
finishedSelector:#selector(viewController:finishedWithAuth:error:)];
[[self navigationController] pushViewController:authController animated:YES];
}else{
[auth beginTokenFetchWithDelegate:self didFinishSelector:#selector(auth:finishedRefreshWithFetcher:error:)];
}
}
and RETAIN the authentication object if signed in successfully, then use that auth object when using google plus services:
GTLServicePlus* plusService = [[[GTLServicePlus alloc] init] autorelease];
[plusService setAuthorizer:self.auth];//!!!here use our authentication object!!!
No need for GPPSignIn.
Full write up is here: Here is Another Solution
Use the (new) Google Sign In iOS SDK.
The SDK natively supports Sign In through WebView when no Google app is present to complete the Sign In process. It also supports potential dispatch to several Google apps for this purpose.

MPMediaPickerController.showsCloudItems seems to do nothing

Posted this on Apple with no luck, but now that the iOS 6 NDA is up, hoping more eyes will see it here.
I am attempting to modify an app to only allow a user to select music that has been downloaded locally. I have the following code under iOS 6 GM:
MPMediaPickerController* mpc = [[MPMediaPickerController alloc] initWithMediaTypes: MPMediaTypeAnyAudio];
mpc.allowsPickingMultipleItems = YES;
mpc.modalPresentationStyle = UIModalPresentationCurrentContext;
mpc.showsCloudItems = NO;
[self presentViewController:mpc animated:YES completion:nil];
From the documentation:
The default behavior for a media item picker is YES, which means the
the picker shows available iCloud items. A media item is considered an
iCloud item if it is available via iTunes Match and is not already
stored on the device.
I take this to mean that if iTunes Match is enabled, only items that have been downloaded to the device will show in the picker, however I always see the entire iTunes Match library. I filed a radar for this, because it seems like a serious bug. If anyone can tell me otherwise, I'd love to know what I'm missing here.
This seems to be an OS problem.
Using picker.showsCloudItems = NO; correctly shows fewer songs, instead of the whole list... The songs listed there are songs that either were manually downloaded in the Music app or songs that were streamed and therefore cached.
The problem, at least in my case, is dealing with the cached ones.
If I select a song that was manually downloaded the value of MPMediaItemPropertyIsCloudItem is NO, which is correct. I can also access the asset's URL through the MPMediaItemPropertyAssetURL property.
On the other hand, selecting a song that was cached returns YES on MPMediaItemPropertyIsCloudItem and nil on MPMediaItemPropertyAssetURL, making the song virtually useless to me.
Sorry I don't have an actual answer but I don't have enough reputation to simply comment.
Hope my 2 cents help somehow, but it truly seems to me that this issue can only be resolved by Apple in a future update.
A better solution to test if an item comes from iCloud in the didPickMediaItems delegate:
MPMediaItem *selectedItem = [selectedItems objectAtIndex:0];
if (![[selectedItem valueForProperty:MPMediaItemPropertyIsCloudItem] boolValue])
You don't really need to play it, it is more efficient to use the embedded property in the MPMediaItem.
I had this same problem. Although I was unable to hide the items, here's a good workaround that I used to prevent people from being able to select them. Inside didPickMediaItems, you should temporarily load it into an AVPlayerItem and then just check the validity of that item like so:
- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
MPMediaItem *selectedItem = [[mediaItemCollection items]objectAtIndex:0];
NSURL *tempURL = [selectedItem valueForProperty:MPMediaItemPropertyAssetURL];
AVPlayerItem *playerItem = [[AVPlayerItem alloc]initWithURL:tempURL];
if(playerItem.loadedTimeRanges==NULL)
{
UIAlertView *alert=[[[UIAlertView alloc]initWithTitle:NSLocalizedString(#"Invalid Song Choice",NULL) message:NSLocalizedString(#"Please choose a song that is local to your phone.",NULL) delegate:self cancelButtonTitle:NSLocalizedString(#"Okay",NULL) otherButtonTitles:nil]autorelease];
[alert show];
[playerItem release];
}
else
{
NSLog(#"Your good to go...do whatever you want with the local song");
}
}
It appears to be fixed in iOS 7.
The following code works; iCloud items are not showing:
MPMediaPickerController *picker = [[MPMediaPickerController alloc] initWithMediaTypes: MPMediaTypeMusic];
picker.delegate = self;
picker.allowsPickingMultipleItems = NO;
picker.showsCloudItems = NO;

How can I improve my TWTweetComposeViewController code in iOS?

I have implemented the following code to do a Twitter Share. In my code I try to test for iOS 5 and if that does not work, I go back to the old way of sharing using ShareKit's Twitter code.
I showed the code to a co worker and he suggested that my code may have flaws and that I need to do two things:
Do a proper Run Time check?? (since it may crash on IOS 4 and earlier) EVEN though it did not.
Weak Link the Twitter frame work
Can someone kindly explain what a proper run time check would be? and Why Weak Link?
NSString *text = [NSString stringWithFormat:#"#Awesome chart: %#", self.titleLabel.text];
if ([TWTweetComposeViewController canSendTweet])
{
TWTweetComposeViewController *tweetComposeViewController = [[TWTweetComposeViewController alloc] init];
[tweetComposeViewController setInitialText:text];
[tweetComposeViewController addImage:image];
[tweetComposeViewController setCompletionHandler:^(TWTweetComposeViewControllerResult result){
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissModalViewControllerAnimated:YES];
if (result == TWTweetComposeViewControllerResultDone)
{
NSLog(#"iOS 5 onwards Twitter share complete");
}
});
}];
[self presentViewController:tweetComposeViewController
animated:YES
completion:^{ }];
}
else
{
SHKItem *item = [SHKItem image:image title:text];
// Share the message.
[SHKTwitter shareItem:item];
NSLog(#"Device does not support Twitter library");
}
}
A weak link simply means the a framework is not required to be on the device. Or put another way, when you add frameworks to your project, the app will require that framework to be on the device. So if you require a framework to be there, and it isn't, then the app will crash. In your case, you would want to weak link the twitter framework if you want the app to run on iOS version prior to iOS 5 (ie iOS 4.x).
A proper run time check means you should load the app onto your device (running iOS 5 or later) and test the twitter feature of your app. If it crashes, then you know you have a problem.
I skimmed your code and everything looks fine. I didn't run it through my complier though so I can't speak for syntax errors and such. The one change I would make is:
if ([TWTweetComposeViewController canSendTweet])
to
Class twClass = NSClassFromString(#"TWTweetComposeViewController");
if (!twClass) // Framework not available, older iOS
return;
The reason why I use that is becuase that literally checks if the framework is on that device, while canSendTweet checks if the user is logged in. So I don't want to confuse a user not being logged in with a user whose device doesn't support Twitter with iOS 5.
Let me know if you need anymore help.
DETweetComposeViewController is another option. It's compatible with iOS 4 too.
I think you also leak the controller (as do most of the code samples I've seen).
On the other hand, you paid attention to the documentation about the completion handler, and correctly make sure you do UI work in the main thread. I need to go fix my implementation to do the same.

getting operator details in iphone

How to get the currently using operator details (like Airtel or Idea or etc..)of iPhone.
Is it possible to get those details or is there any way to identify which operator currently we are using.I am developing an application which is based on the operator, if user changes his SIM(Operator) then the app shouldn't work, it has to work for that particular operator.
CTCarrier should have the info you need in it.
Edit: CTTelephonyNetworkInfo responds to a user switching SIMs mid-session, and provides an instance of CTCarrier for you.
Sample code (in your app delegate):
#import <CoreTelephony/CoreTelephony.h>
static NSString *requiredCarrier = #"Verizon"; // example carrier
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
CTTelephonyNetworkInfo *myNetworkInfo = [[CTTelephonyNetworkInfo alloc] init];
CTCarrier *myCarrier = [myNetworkInfo subscriberCellularProvider];
if(![[myCarrier carrierName] isEqualToString:requiredCarrier])
{
// lock the app, possibly terminate the app after displaying a UIAlertView
// informing the user of the network-lock.
}
// ...
return YES;
}
Note the following advisory on the properties of CTCarrier (I would recommend not doing a permanent one-way lock if a nil carrier is read):
"The value for this property is nil if any of the following apply:
The device is in Airplane mode.
There is no SIM card in the device.
The device is outside of cellular service range."
Of the properties of CTCarrier to validate on, I would recommend carrierName specifically as it doesn't change when the user is roaming so the app will still work as long as the SIM is tied to your desired operator.
Just a quick note in case someone is looking for this.
I have noticed from playing with the CTCarrier API that "nil" is returned to any of its properties only on the emulator. On the device it returns #"" (blank string) for some reason! Checking agains nil failed ont eh device but checking equality with #"" worked!
You can use the Core Telephony framework to achieve this. In particular CTCarrier and the carrierName property. View the documentation here: http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Reference/CTCarrier/Reference/Reference.html%23//apple_ref/doc/uid/TP40009596

Detecting changed user defaults

I have an iPhone app and have implemented local notifications. The app has a page in Settings that allows a user to schedule notifications a specified number of days in advance of an event. To make sure that changes to the settings take effect every time a user activates the app, I have the following in my app delegate:
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[NSUserDefaults standardUserDefaults] synchronize];
[self rescheduleAllNotificationsWithUserNotification:NO];
}
The problem is that the call to rescheduleAllNotificationsWithUserNotification: takes a few seconds and the app feels a little sluggish on start.
I only need to call rescheduleAllNotificationsWithUserNotification: if any of the settings have been changed. Is there a way to detect if the user has changed any of the settings between app activations so I can avoid always rescheduling the notifications?
I think you might be looking for the NSUserDefaultsDidChangeNotification notification.
You can register to listen to this notification and be informed whenever the user's preferences change.
You should use a way to put this method call in another thread is possible. performSelectorInBackground is an easy way to do it:
[self performSelectorInBackground:#selector(rescheduleAllNotificationsWithUserNotification:) withObject:NO];
That should help you get rid of the laggy performance. You could even use ^blocks, as you seem to be on iOS 4.
What I have done in the past, if there are not too many preference values to monitor, is to have alternate versions, sort of "the last time I ran" versions. One set of values is accessible through the Settings application but the other is only set from within the application.
NSString *name = (NSString *) CFPreferencesCopyAppValue( (CFStringRef) #"name_preference", kCFPreferencesCurrentApplication );
NSString *nameLastTime = (NSString *) CFPreferencesCopyAppValue( (CFStringRef) #"name_last_execution", kCFPreferencesCurrentApplication );
// Need obvious checks for empty or missing entries, etc., etc.
if( ![nameLastTime isEqual: name] ) {
// Store the new name as the name at last execution...
CFPreferencesSetAppValue( (CFStringRef) #"name_last_execution", (CFStringRef) name, kCFPreferencesCurrentApplication );
CFPreferencesAppSynchronize( kCFPreferencesCurrentApplication );
[self rescheduleAllNotificationsWithUserNotification:NO];
}
It is not real elegant with fancy object-oriented call-back methods, etc., but it gets the job done cheaply and reliably.