Seems that Apple is moving away from the custom scheme mechanism for opening apps via linking.
With custom schemes if you were to try to open an custom scheme registered by your app, the applications would open and the javascript function handleOpenURL would handle the call.
Does worklight support the new "Universal Links" method in IOS9?
In our project worklight didn't trigger handleOpenURL function for Universal Links functionality out of the box.
So, we used the following solution:
1) Native layer plugin
MyAppDelegate+UniversalLinksPlugin.h
#import "rr.h"
#interface MyAppDelegate (UniversalLinksPlugin)
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler;
#end
MyAppDelegate+UniversalLinksPlugin.m
#import "rr.h"
#import <objc/runtime.h>
#implementation MyAppDelegate (UniversalLinksPlugin)
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
NSLog(#"Universal links plugin: starting application launch handling.");
// ignore activities that are not for Universal Links
if (![userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb] || userActivity.webpageURL == nil) {
return NO;
}
NSString* url = [userActivity.webpageURL absoluteString];
NSLog(#"Universal links plugin: the following url is used for the application launch %#", url);
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
[data setValue:url forKey:#"url"];
[[WL sharedInstance] sendActionToJS:#"handleURL" withData:data];
return YES;
}
#end
2) config.xml update:
<feature name="UniversalLinksPlugin">
<param name="ios-package" value="UniversalLinksPlugin"/>
</feature>
3) iphone/js/main.js update:
/**
* UL links handling
*/
document.addEventListener("deviceready", function() {
WL.App.addActionReceiver ("ULReceiver", function(received) {
if (received.action === "handleURL") {
WL.Logger.debug('Inside handle URL action receiver. Provided url: ' + received.data.url);
handleOpenURL(received.data.url);
}
});
}, false);
This is it. I really hope that this will help to someone )
Universal Linking was not tested as part of iOS 9 support. If linking is required continue to work with the custom scheme option for the time being.
Edit: tested and found to be working.
Related
I'm having an issue getting my React Native iOS app Firebase Dynamic Link to survive the App Store installation process. This link works fine when the app is installation and works perfectly in Android. Dynamic Link urls also work perfectly with Google branded urls such as page.link or goo.gl.
I created a couple of custom domains, properly set the custom url, associated domains, and added them to the FirebaseDynamicLinksCustomDomains array in the plist and still can't get them to work from an app store launch. I also added some logs to the AppDelegate file to see if the url is getting lost somewhere, but I don't see it coming in the native iOS code. I do, however, see the dynamic links showing up when the app is installed and or I use a Google branded domain like page.link. I also tested my domains with Firebase's quickstart app and they don't work unless the app is installed.
The custom domain does work when the app preview screen is enabled, but I would prefer not to use it see there's a branded interstitial page that redirects the user to the App Store if the app isn't installed.
I am using react-native-firebase/analytics 11.1.2, react-native-firebase/app 11.1.2, react-native-firebase/dynamic-links 11.1.2, and react-native-firebase/messaging 11.1.2. I don't know if this is a third party issue or a Firebase SDK issue.
I'm testing for the presence of the url in the native and React code here:
- (BOOL)application:(UIApplication *)application
continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:
#if defined(__IPHONE_12_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_0)
(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))restorationHandler {
#else
(nonnull void (^)(NSArray *_Nullable))restorationHandler {
#endif // __IPHONE_12_0
BOOL handled = [[FIRDynamicLinks dynamicLinks] handleUniversalLink:userActivity.webpageURL
completion:^(FIRDynamicLink * _Nullable dynamicLink,
NSError * _Nullable error) {
if (dynamicLink.url) {
NSString *message = [NSString stringWithFormat:#"Foreground deep link iOS native %#", dynamicLink.url.absoluteString];
[SentrySDK captureMessage:message];
} else {
[SentrySDK captureMessage:#"Foreground deep link iOS native no deep link found"];
}
}];
return handled;
}
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<NSString *, id> *)options {
return [self application:app
openURL:url
sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
annotation:options[UIApplicationOpenURLOptionsAnnotationKey]];
}
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
FIRDynamicLink *dynamicLink = [[FIRDynamicLinks dynamicLinks] dynamicLinkFromCustomSchemeURL:url];
if (dynamicLink) {
if (dynamicLink.url) {
NSString *message = [NSString stringWithFormat:#"Background deep link iOS native %#", dynamicLink.url.absoluteString];
[SentrySDK captureMessage:message];
} else {
[SentrySDK captureMessage:#"Background deep link iOS native no deep link found"];
}
return YES;
}
return NO;
}
And testing for the url in the React code in the App.js file:
useEffect(() => {
dynamicLinks()
.getInitialLink()
.then((link) => {
const deepLink = link ?? 'no deep link available';
logSentryMessage(JSON.stringify(deepLink), 'Deep link from background');
if (isNil(link) || isNil(link.url)) {
return;
}
setUrl(link.url);
});
}, []);
/**
* AppLink handler
*
* #param link
*/
const handleDynamicLink = useCallback(
(link: FirebaseDynamicLinksTypes.DynamicLink) => {
const deepLink = link ?? 'no deep link available';
logSentryMessage(JSON.stringify(deepLink), 'Deep link from foreground');
if (isNil(link.url)) {
return;
}
setUrl(link.url);
},
[],
);
useEffect(() => {
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
return () => unsubscribe();
}, [handleDynamicLink]);
I am trying to use Universal Links. On clicking the link app launches on the iPad but in the delegate call continueUserActivity: userActivity variable is nil. Here is my code in AppDelegate.
- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType
{
if ([userActivityType isEqualToString: NSUserActivityTypeBrowsingWeb])
{
}
return true;
}
-(BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
{
if ([userActivity.activityType isEqualToString: NSUserActivityTypeBrowsingWeb]) {
NSURL *url = userActivity.webpageURL;
NSString *msg = [self getQueryComponentWithName:#"msg" fromURL:url];
}
return YES;
}
I was using Debug build. It works when I use release mode. To build in release mode please use following steps:
Go to Product menu
Select Scheme
Edit Scheme
In Build Configuration Select Release.
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!
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.
I create a handoff request for my apple watch app with the following code:
[self updateUserActivity:#"..." userInfo:selectedTopic webpageURL:[NSURL URLWithString:nil]];
And I handle the request in the phone AppDelegate with the following code:
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *))restorationHandler
{
BOOL handled = NO;
[userActivity becomeCurrent];
NSString *type = [userActivity activityType];
NSDictionary *userInfo = [userActivity userInfo];
if ([type isEqualToString:#"..."]) {
//does action
[userActivity invalidate];
handled = YES;
}
return handled;
}
The issue is the handoff icon does not go away even though this code is executed. What am I doing wrong here? Why will the handoff icon not disappear?
I've seen this behaviour on other applications. The only applications I see with the correct behaviour are native apps.