canOpenUrl fails, but openUrl succeed - ios

I am facing a strange problem.
I am using xcode 7.2, iOS 9, working on real device iphone 4S (not simulator).
I have 2 apps, app1 and app2. app1 is supposed to send data to app2 using an url scheme.
app2 has well declared the scheme
app1 has referenced the scheme in plist (as it is required in iOS9)
<key>LSApplicationQueriesSchemes</key>
<array>
<array>
<string>OpenLinkMyData</string>
</array>
</array>
Here is the code i use :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) , ^{
// build the url, using the scheme name, and the data
// note that json is escaped from bad url chars.
NSString * MyJsonDataWellEscaped = [[SomeClass getJSonDataToExport] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:#"OpenLinkMyData://%#",MyJsonDataWellEscaped]];
// Next line should allow test if the app able to manage that scheme is installed.
// BUT in our case, this allways returning false.
bool can = [[UIApplication sharedApplication] canOpenURL:url];
NSLog(#"canOpenUrl = %#", can?#"true":#"false");
});
// code of the app that do stuff...
}
I get back the following logs :
-canOpenURL: failed for URL: "OpenLinkMyData://(myJsonSuff)" - error: "This app is not allowed to query for scheme OpenLinkMyData"
canOpenUrl = false
But if i use the following code :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) , ^{
// build the url, using the scheme name, and the data
// not that json is escaped from bad url chars.
NSString * MyJsonDataWellEscaped = [[Move2MyMHelper getJSonDataToExport] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:#"OpenLinkMyData://%#",MyJsonDataWellEscaped]];
if([[UIApplication sharedApplication] openURL:url])
{
NSLog(#"App launched OK");
}
else
{
NSLog(#"App not launched");
}
});
// code of the app that do stuff...
}
If I don't check if scheme is available and I use it directly, App2 is well opened and get all data as required.
(else if the app2 is not installed, i get the "App not launched" log).
here is the App2 source for receiving the data (which works as awaited) :
-(BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
NSString *prefixToRemove = #"OpenLinkMyData://";
if(url != nil && [[url absoluteString] hasPrefix:prefixToRemove])
{
NSString * urlStr = [url absoluteString];
NSString * json = [urlStr substringFromIndex:[prefixToRemove length]];
json = [json stringByRemovingPercentEncoding];
NSLog(#"OpenLinkMyData with json : %#", json);
}
return YES;
}
What is the problem with canOpenUrl in my case ?
Thanks for any help.

Making LSApplicationQueriesSchemes be an array of strings instead of an array of arrays of strings:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>OpenLinkMyData</string>
</array>

A side note regarding this topic...
There is a 50 request limit for protocols that are not registered.
In this discussion apple mention that for a specific version of an app you can only query the canOpenUrl a limited number of times and will fail after 50 calls for undeclared schemes. I've also seen that if the protocol is added once you have entered this failing state it will still fail.
Be aware of this, could be useful to someone.

Related

Issues with custom Url Schemes

I have an app lets say 2 apps - app1,app2
Lets say I open from app1 app2.
How can I send data back from app2 with custom url schemes,like in facebook app?
If I reopen app1 with url,I will see go back to app2,and its not good idea for my case.
I want to open app2 like presented modally,and dismiss it with returned data.Is it possible?
You can define custom URL Schema for your app from info.plist file. you can check existing Thread on SO for this.
In your case,for example In app1- define custom url myAppOneScheme and in your app2, Define custom url myAppTwoScheme.
When you open app2 from app 1, Pass app1's url like this:
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"myAppOneScheme://test?callerURL= myAppOneScheme"]];
from app2, handle openURL method:
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
NSLog(#"url recieved: %#", url);
NSLog(#"query string: %#", [url query]);
NSLog(#"host: %#", [url host]);
NSLog(#"url path: %#", [url path]);
NSDictionary *dict = [self parseQueryString:[url query]];
NSLog(#"query dict: %#", dict);
// NSString callerurl = parse callerURL from query
// store callerurl in user default or global variable.
return YES;
}
when you are done with operations in app2 and want to go back to app1, open caller url
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:"%#//testback?response=%#", caller url , datayouwanttoSendback]]];
Now, in your app1, again handle open url method and parse response

Using openScheme with POST

I have an iOS app that need to switch to Safari and open a link using a POST command. Since iOS 10 we are supposed to use 'openScheme', but where do a specify http method POST?
This is where I am so far;
- (void)openScheme:(NSString *)scheme
options:(NSDictionary *)options
{
UIApplication *application = [UIApplication sharedApplication];
NSURL *URL = [NSURL URLWithString:scheme];
[application openURL:URL options:options completionHandler:^(BOOL success) {
if (success) {
NSLog(#"Opened %#",scheme);
}
}];
}
You can achieve it by adding URLSchemes.
Below is the steps.
(i) Open your info tab in targets.
(ii) At the below, you will find URL Types option.
(iii)Fill data as shown in below image.
(iv) Now, open your Safari browser in iPhone/iPad, type YourApp:// and enter. This should open your app.
Hope this helps!

Can I possibly get the parameter in URL Scheme when my app set to not run in background?

I am trying to open my app using URL Scheme with a parameter on it, my problem is, is it possible to get the parameter when the app is set to not run in the background.
SampleApp-info.plist
<key>UIApplicationExitsOnSuspend</key>
<true/>
AppDelegate.m
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;
{
NSLog(#"url recieved: %#", url);
NSLog(#"scheme: %#", [url scheme]);
NSLog(#"query string: %#", [url query]);
NSLog(#"host: %#", [url host]);
return YES;
}
When UIApplicationExitsOnSuspend is set to true and try to open my app from Safari, it's not creating any logs but when I remove the key it is working well. I have to restart my app to load the first viewcontroller and suspending it on the background is the best way, I think...
Can anyone help me on this? or a better way so I can reload my app starting in the first viewcontroller? Newbie here.
I really appreciate any help. Thank you and regards.
UIApplicationExitsOnSuspend Settings
You have to set UIApplicationExitsOnSuspend to false. URL Scheme Callback will not work with UIApplicationExitsOnSuspend set to true. If you want to have a simular behavior like UIApplicationExitsOnSuspend set to true just add the following code
- (void)applicationDidEnterBackground:(UIApplication *)application {
[super applicationDidEnterBackground:application];
exit(0);
}
First you have to make an entry in in Info.plist
e.g. entry your URL Scheme. In this case (in the picture) the callback will be blurry:// the identifier should be normally unique
Callback Implementation
For Pre iOS 9 swift
optional func application(_ application: UIApplication,
handleOpenURL url: NSURL) -> Bool {
NSLog("URL is %#", url)
return true
}
For Pre iOS 9 objective-c
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
// handler code here
NSLog(#"Url is %#, url);
return YES;
}
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
// handler code here
NSLog(#"Url is %#, url);
return YES;
}
IOS 9 objective-c
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<NSString *,
id> *)options {
NSLog(#"Url is %#", url);
return YES;
}
IOS 9 - Swift
optional func application(_ app: UIApplication,
openURL url: NSURL,
options options: [String : AnyObject]) -> Bool {
NSLog("URL is", NSURL)
return true
}
Test the callback
Just open the weburl in the example case blurry://test with the mobile safari on your Handy

Calling openURL from Today widget opens the app but does not call application:openURL:sourceApplication:annotation:

I set a Today Extension for my containing App.
Here is the code:
TodayViewController:
[self.extensionContext openURL:[NSURL URLWithString:[NSString stringWithFormat:#"idaxiang://action=%ld",(long)aCell.tag]] completionHandler:^(BOOL success) {
NSLog(#"open url result:%d",success);}];
AppDelegate.m:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{
NSString* prefix = #"idaxiang://action=";
if ([ElephantUtils originString:[url absoluteString] withTargetString:prefix]) {
if ([[url absoluteString] rangeOfString:prefix].location != NSNotFound) {
NSString* action = [[url absoluteString] substringFromIndex:prefix.length];
UINavigationController *navc = (UINavigationController *)application.keyWindow.rootViewController;
if (navc.viewControllers.count > 1) {
[navc popToRootViewControllerAnimated:NO];
}
ElephantHomeViewController *homeVC = (ElephantHomeViewController *)navc.topViewController;
[homeVC PushToSpecificArticle:[action integerValue]];
}
return [[Diplomat sharedInstance] handleOpenURL:url];
}
But the weird thing is that if the containing app not open (not load to memory), I tap the cell from Today Extension, the app will open but can't call application:openURL:sourceApplication:annotation:, so it will not push to target viewcontroller. But now, the app is opened (have loaded to memory), I tap the cell from Today Extension, the containing app will call application:openURL:sourceApplication:annotation: and push to target viewcontroller successfully.
Here is my info.plist:
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.smartisanelephant.www</string>
<key>CFBundleURLSchemes</key>
<array>
<string>idaxiang</string>
</array>
</dict>
Please give me some advice, I have struggled this problem for some days. Thanks indeed!
I have not enough reputation to comment, but
is this method being called when you try to launch it from extension ?
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url

how to manage sqlite database lite version to paid version in iphone application?

I created iPhone application using sqlite db. In this sqlite db I have stories document, directory images url, and other parameters. When the user is using the "lite" version of the app, everything works fine. But when I upgrade app from "lite" version to a "paid" version, I'd like to be able to copy database and latest files in my Documents directory to the "paid" app. Assistance would be appreciated.
If the data base in Documents directory then while updating your app iOS keep the old database.
Because of app sandboxing, you can't do precisely what you want, but there are a number of approaches:
Instead of separate pro version, only have a single version of the app, which offers an In App Purchase that enables certain features. See Convert the "lite app" to "pro app" within the application. Thus, this eliminates the need to import files/settings from one app to another.
You could implement separate custom URL scheme for both free and pro versions of your app to allow them to exchange limited amount of data via a URL. Thus, the pro app would probably, on the first time it's run, check to see if the lite app is installed, and if so, request data from it:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
BOOL runAlready = [userDefaults boolForKey:kRunAlready];
if (!runAlready)
{
[userDefaults setBool:YES forKey:kRunAlready];
[userDefaults synchronize];
[self importDataFromLite];
}
}
- (void)importDataFromLite
{
NSURL *liteURL = [NSURL URLWithString:#"testapp-lite://getdata"];
// if we can open test app, then test app installed, so launch it, providing "getdata" request
if ([[UIApplication sharedApplication] canOpenURL:liteURL])
{
// Getting data from lite app
[[UIApplication sharedApplication] openURL:liteURL];
}
}
The lite version would obviously have to set up its Info.plist to register this custom URL scheme, and its app delegate would need to respond to this request for data, in turn calling a custom URL scheme of the pro app to send the data back:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
[[[UIAlertView alloc] initWithTitle:nil message:[url absoluteString] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
if ([[url absoluteString] isEqualToString:#"testapp-lite://getdata"])
{
// I'm going to create URL from local NSDictionary, but you would presumably retrieve data from your model/database
NSDictionary *dictionary = #{#"name" : #"Rob", #"age" : #"29"};
NSMutableArray *array = [NSMutableArray array];
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
[array addObject:[NSString stringWithFormat:#"%#=%#", key, [obj stringByAddingPercentEscapesForURLParameterUsingEncoding:NSUTF8StringEncoding]]];
}];
// now create the URL
NSURL *proURL = [NSURL URLWithString:[NSString stringWithFormat:#"testapp-pro://data?%#", [array componentsJoinedByString:#"&"]]];
// if we can open pro app, then then do so, providing "getdata" request
if ([[UIApplication sharedApplication] canOpenURL:proURL])
{
NSLog(#"Opening pro app");
[[UIApplication sharedApplication] openURL:proURL];
}
}
return YES;
}
In turn, the pro version could have custom URL scheme to receive the data from the lite version of the app. Thus, the pro app's handler for the data from the lite app might look like:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
NSArray *absoluteStringComponents = [[url absoluteString] componentsSeparatedByString:#"?"];
NSArray *parameters = [absoluteStringComponents[1] componentsSeparatedByString:#"&"];
for (NSString *parameter in parameters)
{
NSArray *parameterComponents = [parameter componentsSeparatedByString:#"="];
// I'm just logging the results, but you'd presumably integrate the results into your model
NSLog(#"%# is equal to %#", parameterComponents[0], [parameterComponents[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]);
}
return YES;
}
This works with modest amounts of basic data, but I would not have thought it would be well suited for exchanging image files. If interested, I can show you example of this.
In a variation of the above, you could use the UIDocumentInteractionController to import larger amounts of data, though I think this would request you to present a popover in the lite app for the user to specify to open the data file in the pro app (which seems inelegant).
You could, theoretically, store data on the cloud, possibly iCloud, DropBox, or your own server.

Resources