How to Support Universal Links in iOS App and Setup Server For It

How to support Universal Links in iOS App and setup server for it?

Support Universal Links

When you support universal links, iOS 9 users can tap a link to your website and get seamlessly redirected to your installed app without going through Safari. If your app isn’t installed, tapping a link to your website opens your website in Safari.

Here, how to setup your own server, and handle the corresponding links in your app.


Setup Server

You need to having a server running online. To securely associate your iOS app with a server, Apple requires that you make available a configuration file, called apple-app-site-association. This is a JSON file which describes the domain and supported routes.

The apple-app-site-association file needs to be accessible via HTTPS, without any redirects, at https://{domain}/apple-app-site-association.

The file looks like this:

{
"applinks": {
"apps": [ ],
"details": [
{
"appID": "{app_prefix}.{app_identifier}",
"paths": [ "/path/to/content", "/path/to/other/*", "NOT /path/to/exclude" ]
},
{
"appID": "TeamID.BundleID2",
"paths": [ "*" ]
}
]
}
}

NOTE - Don’t append .json to the apple-app-site-association filename.

The keys are as follows:

apps: Should have an empty array as its value, and it must be present. This is how Apple wants it.

details: Is an array of dictionaries, one for each iOS app supported by the website. Each dictionary contains information about the app, the team and bundle IDs.

There are 3 ways to define paths:

Static: The entire supported path is hardcoded to identify a specific link, e.g. /static/terms

Wildcards: A * can be used to match dynamic paths, e.g. /books/* can matches the path to any author’s page. ? inside specific path components, e.g. books/1? can be used to match any books whose ID starts with 1.

Exclusions: Prepending a path with NOT excludes that path from being matched.

The order in which the paths are mentioned in the array is important. Earlier indices have higher priority. Once a path matches, the evaluation stops, and other paths ignored. Each path is case-sensitive.

Supporting Multiple Domains

Each domain supported in the app needs to make available its own apple-app-site-association file. If the content served by each domain is different, then the contents of the file will also change to support the respective paths. Otherwise, the same file can be used, but it needs to be accessible at every supported domain.

Signing the App-Site-Association File

Note: You can skip this part if your server uses HTTPS to serve content and jump to Application Setup guide.

If your app targets iOS 9 and your server uses HTTPS to serve content, you don’t need to sign the file. If not (e.g. when supporting Handoff on iOS 8), it has to be signed using a SSL certificate from a recognized certificate authority.

Note: This is not the certificate provided by Apple to submit your app to the App Store. It should be provided by a third-party, and it’s recommended to use the same certificate you use for your HTTPS server (although it’s not required).

To sign the file, first create and save a simple .txt version of it. Next, in the terminal, run the following command:

cat .txt | openssl smime -sign -inkey example.com.key -signer example.com.pem -certfile intermediate.pem -noattr -nodetach -outform DER > apple-app-site-association

This will output the signed file in the current directory. The example.com.key, example.com.pem, and intermediate.pem are the files that would made available to you by your Certifying Authority.

Note: If the file is unsigned, it should have a Content-Type of application/json. Otherwise, it should be application/pkcs7-mime.

Validate your Server with Apple App search validation tool

Test your webpage for iOS 9 Search APIs. Enter a URL and Applebot will crawl your webpage and show how you can optimize for best results
https://search.developer.apple.com/appsearch-validation-tool/

Website Code

The website code can be found gh-pages branch on
https://github.com/vineetchoudhary/iOS-Universal-Links/tree/gh-pages


Setup iOS Application

Application will target iOS 9 and will be using Xcode 7.2 with Objective-C.

Enabling Universal Links

The setup on the app side requires two things:

1. Configuring the app’s entitlement, and enabling universal links.

2. Handling Incoming Links in your AppDelegate.

1. Configuring the app’s entitlement, and enabling universal links.

The first step in configuring your app’s entitlements is to enable it for your App ID. Do this in the Apple Developer Member Center. Click on Certificates, Identifiers & Profiles and then Identifiers. Select your App ID (create it first if required), click Edit and enable the Associated Domains entitlement.

Sample Image

Next, get the App ID prefix and suffix by clicking on the respective App ID.

The App ID prefix and suffix should match the one in the apple-app-site-association file.

Next in Xcode, select your App’s target, click Capabilities and toggle Associated Domains to On. Add an entry for each domain that your app supports, prefixed with applinks:.

For example: applinks:vineetchoudhary.github.io

Which looks like this for the sample app:
Sample Image

Note: Ensure you have selected the same team and entered the same Bundle ID as the registered App ID on the Member Center. Also ensure that the entitlements file is included by Xcode by selecting the file and in the File Inspector, ensure that your target is checked.

Sample Image

Handling Incoming Links in your AppDelegate

[UIApplicationDelegate application: continueUserActivity: restorationHandler:] method in AppDelegate.m handles incoming links. You parse this URL to determine the right action in the app.

For example, in the sample app:

Objective-C

-(BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler{
if ([userActivity.activityType isEqualToString: NSUserActivityTypeBrowsingWeb]) {
NSURL *url = userActivity.webpageURL;
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UINavigationController *navigationController = (UINavigationController *)_window.rootViewController;
if ([url.pathComponents containsObject:@"home"]) {
[navigationController pushViewController:[storyBoard instantiateViewControllerWithIdentifier:@"HomeScreenId"] animated:YES];
}else if ([url.pathComponents containsObject:@"about"]){
[navigationController pushViewController:[storyBoard instantiateViewControllerWithIdentifier:@"AboutScreenId"] animated:YES];
}
}
return YES;
}

Swift :

func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
let url = userActivity.webpageURL!
//handle url
}
return true
}

iOS Application Code

The app code can be found master branch on
https://github.com/vineetchoudhary/iOS-Universal-Links/

Note:

  1. Generally, any supported link clicked in Safari, or in instances of UIWebView/WKWebView should open the app.
  2. For iOS 9.2 and less, this will only work on a device. iOS 9.3 (still in beta at the time of writing) also supports the simulator.
  3. iOS remembers the user’s choice when opening Universal Links. If they tap the top-right breadcrumb to open the link in Safari, all further clicks will take them to Safari, and not the app. They can switch back to opening the app by default by choosing Open in the app banner on the website.

Done. It's all about Universal Links.


References

  1. Support Universal Links in your iOS App

How to setup Associated Domains / Universal Links

First at your device logs and your JSON format it seems your should be following the example shown here: https://developer.apple.com/documentation/safariservices/supporting_associated_domains

In your logs it may shows logs from other apps who also have the outdated API format. However this should not be so much of an issue.

{
"applinks": {
"details": [
{
"appIDs": [ "ABCDE12345.com.example.app", "ABCDE12345.com.example.app2" ],
"components": [
{
"#": "no_universal_links",
"exclude": true,
"comment": "Matches any URL whose fragment equals no_universal_links and instructs the system not to open it as a universal link"
},
{
"/": "/buy/*",
"comment": "Matches any URL whose path starts with /buy/"
},
{
"/": "/help/website/*",
"exclude": true,
"comment": "Matches any URL whose path starts with /help/website/ and instructs the system not to open it as a universal link"
},
{
"/": "/help/*",
"?": { "articleNumber": "????" },
"comment": "Matches any URL whose path starts with /help/ and which has a query item with name 'articleNumber' and a value of exactly 4 characters"
}
]
}
]
},
"webcredentials": {
"apps": [ "ABCDE12345.com.example.app" ]
},
"appclips": {
"apps": ["ABCED12345.com.example.MyApp.Clip"]
}
}

Secondly following the steps in Apple's troubleshooting and your problem I am guessing your problem might be Step 7

  • Your app returned false from one of the following
    UIApplicationDelegate protocol methods:
    application(:continueUserActivity:restorationHandler:),
    application(
    :willFinishLaunchingWithOptions:),
    application(_:didFInishLaunchingWithOptions:).

    This can happen if you
    parsed the URL that is passed into these methods, and you implemented
    logic to determine that your app can not use this URL.

You have not posted how you handle the URL. You need to implement the UIUserActivityRestoring method as suggested in the other answer for it to work. Here is a SwiftUI version

ContentView()   
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in

print("Continue activity \(userActivity)")
guard let url = userActivity.webpageURL else {
return
}

print("User wants to open URL: \(url)")
// TODO same handling as done in onOpenURL()

}

Also add

.onOpenURL { url in
print("URL OPENED")
print(url) // parse the url to get someAction to determine what the app needs do
if url.relativeString == "example://success" {

}
}

https://developer.apple.com/library/archive/qa/qa1916/_index.html

iOS Universal Links are not opening in-app

There are a few possible issues.

  1. Try pasting your domain into this link validator and make sure there are no issues: https://limitless-sierra-4673.herokuapp.com/ (credit to ShortStuffSushi -- see repo)

  2. iOS logs an error message in the system logs if you don't have TLS set up properly on the domain specified in your entitlements. It's buried in the OS logs, not application logs. The error message will look like Sep 21 14:27:01 Derricks-iPhone swcd[2044] : 2015-09-21 02:27:01.878907 PM [SWC] ### Rejecting URL 'https://examplecustomdomain.com/apple-app-site-association' for auth method 'NSURLAuthenticationMethodServerTrust': -6754/0xFFFFE59E kAuthenticationErr. Error message pulled from here, quick (incomplete) instructions on using CloudFlare for TLS here.

  3. In my personal testing, clicking/typing in a link in Safari has never once opened the app directly. Clicking from other apps (iMessage, Mail, Slack, etc.) has worked. Others have reported that clicking links in Google search results have opened the app directly.

  4. Note that if a Universal Link succeeds in opening your app and then you click through to Safari (by tapping your site in the top right corner of the nav bar in app), then iOS stops opening the app when you visit that URL. Then in Safari, you can pull down to reveal a banner at the top of the page with "Open". I wasted a lot of time on this. Note that clicking through to the site => disabling UL seems path specific, based on the paths you specify in the apple-app-site-assocation file. So if you have separate routes, yoursite.com/a/* and yoursite.com/b/*, if you click yoursite.com/a/* and it opens your app directly, you then have the option in the top right corner of the app to click through to yoursite.com/a/*. If you do that, subsequent visits to yoursite.com/a/* will open in browser, not app. However, yoursite.com/b/* should be unaffected and still open your app directly.

Let me know if you discover what the issue is. I'm personally very curious about how Universal Links work and what edge cases exist. Good luck.

Open iOS application from universal links

Not all apps will open universal links in you app. You can read a little more here:

"Pasting a Universal Link directly into the Safari URL field doesn’t cause the app to open automatically. If you do this, you will have to manually pull the website down so that a prompt will appear at the top asking you to open the respective app.
But, if you paste links in Facebook(app), Twitter(app), Mail(app) or even if you go to Facebook on Safari and then click on a universal link, the app opens directly.
Universal links will not works for all the apps in iOS. If you click on a universal link from any of the “BLACK LISTED” apps, it will not open the app. Go to this link to know more.
As in step one, for the first time you will have to manually pull down the website and click “open” to open the link using the respective app. The iOS will “remember” to open the app instead of opening the safari if the universal link with the registered domains are clicked."

https://medium.com/@abhimuralidharan/universal-links-in-ios-79c4ee038272

https://docs.branch.io/pages/deep-linking/universal-links/#appsbrowsers-that-support-universal-links

will iOS universal links support work without web server?

Custom URL Scheme solved my problem. used cordova-plugin-customurlscheme plugin and configured my custom url schema.



Related Topics



Leave a reply



Submit