[ Applause ]
CONRAD SHULTZ: Hi, my name is Conrad Shultz and I'm an engineer on the Safari and WebKit team and this is Session 509, Seamless Linking to Your App.
So, if you're here at WWDC, you almost certainly have an app and odds are you have a website for that app.
So a quick show of hands, how many of you have a website that allows users to access some of the same content or functionality as in your app?
Great. This talk is for you.
So we are going to talk about how we can tear down the walls between your app and your website and create a tight association between them.
As an example of what we are going to talk about, I would like to begin with a demo of an app that you probably have installed on your iPhone or iPad right now, the WWDC app.
So here I have a message from my colleague, Jonathan, telling me to check out a keynote.
And that sounds like a good idea, and I have the WWDC app installed on this iPad.
And I see this is a web link so it will probably still open in Safari.
Let's go ahead and tap on it.
And, huh, that's pretty cool, it took me right to the WWDC app.
Maybe you missed that.
Let's do that again.
I will go ahead and tap on back to messages in the upper left.
I see this is just a regular HTTPS link going to Developer.Apple.com, and I tap on it, and, boom!
Right to the WWDC app, right to the keynote.
No ping ponging through Safari.
[ Applause ]
So we are going to talk about three cool technologies to help you tear down those walls between your app and your website.
First, the magic you just saw in the demo, where the WWDC app opened to the right place when a regular link was tapped.
Next, we are going to talk about Smart App Banners, which allow users to discover your app when they visit your website.
Finally, shared web credentials, which allows your app to access Safari-saved passwords and, therefore, make logging into your app a breeze for your users.
To start us off, I would like to invite my colleague, Jonathan to the stage to talk to us about linking to your app.
[ Applause ]
JONATHAN GRYNSPAN: Thank you, Conrad.
Hello everyone, my name is Jonathan Grynspan and I'm a Core Services Software Engineer here at Apple.
Today I would like to take a moment to talk to you about your app's place in the universe.
I saw a lot of hands go up when Conrad asked how many of you have both an app and a website.
And it's not surprising.
Many modern apps are more than just what the user downloads.
They also have a strong web presence.
Often web content mirrors app content.
Many of you have a great website with great functionality, and we like how your users are able to reach that functionality without even having to install an app.
We also like when your apps work together.
An app exists as part of an ecosystem.
If your app, or website, needs to access content provided by another app, or website, it needs to communicate with it somehow.
For example, if you have an app for foodies and you want to link to a recipe, you might want to use an app or service, such as Yumly.
So how does your app get to Yumly, and how does it provide Yumly's content correctly and quickly?
On iOS we usually use URLs to send data from one app to another.
So let's take a look at how we do that today.
Many iOS apps implement what's called a custom URL scheme.
These are a very effective tool, because they let apps communicate with each other but they do have a few pitfalls.
First off, they don't always map to the right app, because there is no app that truly owns them.
Two apps can claim the same URL scheme and users don't really have a good way to say which app they mean.
Second off, if your app is not installed, they fall flat.
The URL doesn't open and the app needs custom code in order to handle this fairly common situation.
And finally, they make protecting user's privacy difficult.
Apps need to be able to detect if other apps are installed on a device, in order to use custom URL schemes.
This means that they can sniff out if a user has a dating app, or a banking app, or a politically-charged app installed.
This is personal information, and users do not want to share it.
So, there has to be a better way, but what does that better way look like?
Well, obviously, it needs to be just as easy to use as custom URL schemes, so we are still talking about a kind of URL.
It must have verifiable ownership, URLs should always open in the right app.
There should be a strong two-way association between your app and your URLs.
It must have the ability to fall back gracefully, if your app is not installed.
The URL should still work when a user taps on it.
Safari, and web browsers in general, are a great way to display rich content, and are present on nearly every platform, so we'll make sure that these links can open in a web browser.
And it absolutely must protect a user's privacy.
When I run an app on my device, it has no right to know that I also have your app installed.
And, of course, the links that, the excuse me, and, of course, the links that you tap are between you and your device.
There is no need for a third party to be involved.
As Conrad showed you, web URLs are the ideal solution that fit all of these criteria.
You know when they are going to open, they work nearly everywhere, and no third party needs to be involved when you tap one.
Now that we have identified web URLs as our solution, let's break one down.
I know you have all seen a URL before, but if we take a look at the components of a URL, we'll have a better idea of how these links are going to work.
Here is a perfectly normal web URL.
If you are curious, this links to last year's WWDC keynote video.
This is not a new URL that we had to generate just for this purpose, or just for this app.
It already existed, and you can visit it on the Apple Developer Connection website today.
If I open it in Safari, I expect to see exactly the content that you would expect, the video and information about it.
If I - uh, I also expect to see the same content if I open this on a Mac, or on another platform entirely, they are universal.
And thanks to universal links, this link will open in our WWDC app without a web browser getting involved.
So, in order for that to work, iOS needs to look for a few things.
The first thing we look at is the scheme.
We support HTTPS and HTTP URLs.
Next up is the domain, or host name.
The domain is securely associated with your app, by using an SSL certificate to sign a file that is stored on your secure web server.
I will have more info on that in just a moment.
After we have analyzed the scheme and the domain, we take a look at the path.
The path can either match exactly, or it can match with a prefix.
In both cases, you specify the matches, uh, sorry, you specify the matches and you are in control of the situation.
This is useful if you have content that is on your website but is not representable in your app yet.
If a URL does not match, it opens in Safari just like any other link would.
If we do match a URL and we need to - sorry, if we do match a URL and send it to you, you can use NS URL components to break it down, and to get at the information in the URL that's useful to you.
I particularly like using NS URL Query Item because it makes accessing the components of the query super simple.
Universal links mean having your app, not Safari, handle links to your website, but in order to get in order to open your app, when a user taps a link to your website, iOS needs to know that your app should open that link.
Your app must claim that it can handle links from your website.
And iOS then contacts your website, and confirms that your site knows about your app, and is willing to be represented by it.
So let's get your server ready.
There are four steps in this process.
The first step is to create a special file called the Apple-app-site-association file that iOS downloads from your web server.
This file contains specially structured JSON data.
If you adopted Handoff from the Web or Shared Web Credentials last year, this should look familiar.
The top level key here is applinks, which is sibling to web credentials or activity continuation if you already have either of them.
The apps key is important and should be present with an empty array as its value.
Under the new details key, you will see the dictionary.
The keys for this dictionary are your application identifiers, which consist of your team ID, which you can get from the Developer portal, followed by a period, followed by your bundle identifier.
If you have more than one app, you can specify each of them under a separate key.
The paths array tells us what parts of the website are supported by your app.
If you can support your entire website in your app, you only need to include a single asterisk like you see here, but on day one, it's quite possible that your app won't support every single page on your website.
So we have given you the ability to specify which pages it can handle.
As you can see, I have now added two paths to the array.
The first is an exact match.
If a user taps a link that goes to exactly that path, it will open in this app.
If an entire section of your website is supported by your app, you can use a path prefix, such as the second entry in this array, to specify that that section will open in your app.
Notice the asterisk at the end, which tells us that this is a path prefix.
It's important to note that these paths are case sensitive and that other URL components, such as the query or the fragment, are not supported.
The second step is to procure an SSL certificate from a recognized certificate authority.
This is not the same certificate that you use to sign your app for the App Store it's not generated by Apple.
We suggest that you use the same certificate that you use for your HTTPS server, but it's not required.
If you have more than one domain that you need to represent in your app, we suggest you consider getting what's called a wild card certificate.
For more information on certificate generation, please contact your certificate authority.
Step 3 is to sign the JSON file you just created using your SSL certificate.
In order to do this, you will need to use the openssl smime command in terminal on your development Mac.
These two file names are important.
The first one is the name of the file that you just created, the unsigned JSON file that's on your development Mac.
The second must match exactly what you see here, apple-app-site-association.
These are your private key, which you generated during certificate generation, and the certificate that was given to you by your certificate authority.
Once again, this is not a certificate given to you by Apple.
It's neither your App Store certificate, nor your developer ID certificate.
There is one last note to make here, if you have an intermediate certificate that was given to you by your certificate authority, you will need to include during signing.
The fourth and final step, now that you have a signed JSON file, is to upload it to your web server.
This goes at the root of your server.
For example, www.example.com / apple-app -site-association.
It's important to note that each app that your domain supports may need a separate JSON file that's signed appropriately for that domain.
For example, example.com and www.example.com are distinct.
We introduced this functionality in iOS 8, in support of two features: Handoff from the Web and Shared Web Credentials.
In iOS 9, we are adding support for universal links, and we want to make it as easy as possible for you to adopt universal links in your app as soon as possible.
So-sorry, starting in seed 2 of iOS 9, we will no longer require you to sign your apple-app-site-association file.
Simply upload the unsigned JSON thank you [applause].
Simply upload the unsigned JSON file with the same file name, apple-app site-association, to your HTTPS server, and iOS will take care of the rest.
Note that the signing requirement remains in effect, if you need to support iOS 8 for backwards compatibility.
If you are starting with iOS 9, you do not need to fine the file.
Now, let's talk about adoption within your application.
You will need to modify your application delegate, and you will need to adopt an entitlement.
I will show you how to do both.
First off is the application delegate.
If you adopted Handoff last year, this should look familiar.
This method is sent to your application delegate, when a user activity is delivered to your application.
User activity objects - excuse me - user activity objects have a web page URL property that, in the case of this feature, is always an HTTPS or HTTP URL, it's never nil for this feature.
The activity type, in the case of universal links, is always NS User Activity Type Browsing Web.
Once you have confirmed that the activity type matches what you are expecting, you can break down the URL from the web page URL using the NSURLComponents class.
Just like with custom URL schemes, it's possible that the URL doesn't represent anything in your app, or it represents outdated or malformed content.
If that occurs, you should detect it and fail gracefully.
Now, gracefully here, is app specific.
It may be appropriate for you to present an alert to the use, explaining what went wrong, or it may be a better choice for you to open the link in Safari, which you can do from your app, by calling UI Application Open URL.
The most important thing to take away from this, is that users are not left staring at a blank View Controller.
Once your application delegate has been updated, you will need to update your app's entitlements.
This is easy with XCode.
Navigate to your projects settings, and to the configurations, sorry, the capabilities tab there, and select associated domains.
You will want to add an entry for each domain that your app supports: app links, followed by a colon, followed by the domain name.
Once again, every domain that your app supports needs a separate entry here, they are distinct.
Example.com and www.example.com need separate entries, as you see here.
I will show you how this all comes to together in just a moment.
But first I would like to review a couple of best practices.
As with all content coming from outside your app, data may be malformed and you may need to validate it.
If it is, that doesn't mean that you have made a mistake, but it does mean that you will want to fail gracefully when it occurs.
Once you have a URL to process, you should keep in mind that HTTP has caveats on iOS 9 for the sake of user security and privacy, if you transport data using, for instance, NSURLSession, you should always be using HTTPS.
If you need to use unencrypted HTTP in your app, you should review Session 711, Networking with NSURLSession, which covers an important new networking technology called App Transport Security.
Now, let's take a look at how this all fits together in XCode.
I will be showing you code from the WWDC app that's available on the App Store.
So I have here the WWDC project, and I have created a new extension that's going to house the code we are looking at today.
This extension is on my application delegate class and the first thing I'm going to do is add the application delegate method that we discussed.
Now, again, if you adopted Handoff last year this is the same delegate method.
You'll just need to extend it to support this new functionality.
The parameter we are interested in, is the user activity parameter, which contains information on what the user is doing, which in this case is navigating from a website.
We are interested in the activity type and the web page URL.
For our purposes, the activity type is always NSUser activity type BrowsingWeb.
However, if your app supports user activities coming from other sources, such as Handoff, then you will need to check the activity type as I have done here.
Because it's NSUser activity type BrowsingWeb, I know there is a web page URL and that it's not nil, so I have used an explicit unwrap of the web page zero property to get the URL from it.
I then send it to this function, present URL, which is responsible for breaking down the URL and delivering it to the user interface so that the user can see it.
The signature of this function is fairly straight forward.
It takes a single URL as input, and returns a Boolean, saying whether or not we were able to handle the URL.
Now, the body of the method is a little bit more complex, but I will walk you through it line by line.
The first thing we do is create an instance of NSURL Components.
This class, as I said before, breaks down the URL for you and allows you to access the individual parts of it, without you having to build your own parser.
Once we have that object, we extract a few components from it that we are interested in here.
For the WWDC app, we are interested in the host and the path components.
Now, the WWDC app can handle URLs coming from more than one domain, so I want to check the domain to make sure that it's what I'm expecting, so I have got a switch statement here, with a case of developer.apple.com, which is the URL that Conrad showed you earlier, or sorry, the host name for that URL.
Once I have matched that, I want to match the path, and I use a switch statement, which is very powerful in Swift, to gather all of the components at once, and compare them in a single line, s/videos,/WWDC, something that may or may not be a year.
I want to take a look at that value, which is a string right now, and turn it into an Int.
So I cast it with the Int initializer, excuse me, and I also gather the session number, which in this case would be Session 509.
In the case of the example Conrad showed you, it was Session 101.
Now, this Find Session method I will show you in just a second.
If those components have matched, then I want to present it to the user, but I need to validate the input first because it's possible, it's possible that it isn't in a range that makes any sense.
For instance, a year before Apple was founded or a session number in the millions.
So I take the year in session, and compare them against two ranges, 2011 to 2015 for the year, 100 to 10,000 for the session number.
And if it's in those ranges, then it looks like something that's valid that we can present to the user.
So we proceed to present it.
Now, I mentioned this Find Session method which I'm going to show you right now.
There we are.
And this is specific to our app.
It's unlikely you will have this exact same code, so I'm not going to go into too much detail here, but the idea is that the data that's coming in may come from more than one source, so we want to check all of them.
The first thing we look at is the fragment, which in the case of URLs from 2014 and before, contained the session ID, and then if that didn't contain the session ID, we take a look at the query which we have broken apart with NSURL query item.
In this case the query item's property is already populated for us.
We iterate over, and we find the one that either matches ID, or instance.
Either one may contain the session number for us.
Now, I'm almost done with the code here, but there is something missing.
There is a lot of return falses here.
There is a lot of cases where we weren't able to handle the URL.
And, again, that's okay because we may get invalid input from time to time, but up here in my application delegate, I'm not doing anything with that return value, and if I get a URL that's clearly invalid, the user is not going to see anything.
The WWDC Apple opened, and they will be presented with a blank View Controller, which is exactly what we want to avoid.
So we are going to fail gracefully and it's very, very easy to do that.
In this case, if present URL returns false, then I know that I didn't present anything to the user, and I pass the URL back to Safari using UIApplication openURL.
Those are all of the code changes you need to adopt this.
If you have already got code for custom URL schemes, it's quite likely it will look very similar to what we have here.
Now, I'd like to show you what this app looked like before and after we added this code, but before I do that, I need to add an entitlement.
So I go to my Project Settings, I select the Capabilities tab, and I select Associated Domains.
I'm going to add a single entry here, because we are only working with a single domain today, but once again, if you have more than one domain, example.com; www.example.com, they will need separate entries in this entitlement.
In this case, I need applinks:developer.apple.com.
I'm ready to go on my client side.
So I am going to show you what the app looked like before we added this.
So here I have got my iPad, and this has a copy of the WWDC app installed before we added, sorry, before we added universal links to it.
And I'm going to go to the same text message, sorry, the same iMessage I sent Conrad earlier.
It's exactly the same message, but when I tap it because I haven't adopted universal links, it just opens in Safari.
Safari is a great app and it can show me some fantastic content, and in particular, it doesn't give me all the options that I can get from the app.
I can watch the video in both the app and Safari, but, I can also provide feedback from the app, or, make it a favorite to watch later, I can even download it for offline viewing.
So, as I showed you, we have this now in our app, in the app you have in your hands right now, and that looks a little bit like this.
So, once more, this is the URL that I sent Conrad earlier, and I tap it, and just as before, it opens the keynote and I can add it to my favorites, I can watch the video, download it, and so forth.
And that is pretty much everything we had to do [applause].
So before I hand the remote back to Conrad, I would like to thank you for coming, and I look forward to seeing what all of you are going to create using universal links in iOS 9.
[ Applause ]
CONRAD SHULTZ: Great.
Thank you Jonathan.
So by adopting universal links you will give all of your users the best possible user experience.
For users who have your app installed on your iPhone or iPad, links to your website will open immediately in your app without any distracting flashes through Safari.
If a user does not have your app installed, your links will still seamlessly open in Safari.
And for the same reason, your links will still just work on the Mac or even on other platforms.
Now, because this is all handled locally on the user's device, there are no wasteful extra round trips out to the user's web server or out to your web server.
And, of course, this also means the neither Apple nor any other third party can track what your users are doing.
So we have talked a little, or, we have talked about how these links will take the user to your website if they don't have your app installed.
Once there, many of you probably want to help your user discover and install your app.
And ILS has a technology that can help with that called Smart App Banners.
Let's spend just a couple of minutes talking about Smart App Banners which is also about the time it will take for you to adopt them.
So what are Smart App Banners?
They are unobtrusive UI elements that help route users to your app, and which are presented automatically by Safari on iOS.
If a user doesn't have your app installed, the banner will offer to launch the App Store so that the user can download it.
If the user does have your app installed, the banner will offer to launch the app for the user.
Smart App Banners are trivial to add to your website.
And new with iOS 9's app search feature, Smart App Banners will be used by Apple to help index your app and its content, which increases the likelihood of the user finding and installing your app.
Now, some of you might use custom URL schemes to launch the user into your app from your website.
This will still work, but it's not recommended.
And there is no guarantee that the user experience with custom URL schemes will remain the same in the future.
Smart App Banners afford the preferred user experience.
So to adopt Smart App Banners, all you need to do is add one line of HTML to your website's head section.
The apple-itunes-app meta tag will tell Safari that you want to install, or display, a Smart App Banner.
The content attribute must include your app ID, which is used to route the user to the correct app.
You can obtain your app ID using the convenient iTunes link maker.
The app argument should include the URL of the page that is currently being displayed.
This is for a couple of reasons.
First, this allows you to customize the view that you show to the user.
And it's passed to your application open URL source application annotation method.
Second, it is, it helps make sure that app search recognizes your site as more than just a landing page.
And for more information on this point, I would refer you to the Introducing Search API session.
You can find the video of it.
It was held yesterday.
Finally, note the Smart App Banners will not appear in the iOS simulator, so be sure that you test on a physical device.
Once you have adopted Smart App Banners, and once your app can handle links to your website, you have a really cool opportunity to simplify your website.
It no longer needs to be a promotional vehicle for your app.
It can instead focus on presenting your content in the best manner possible.
You no longer need to be in the business of displaying banners or splash screens, or writing code to handle custom URL schemes.
Your website can now be simpler to use and simpler to write.
Okay. So we have talked a fair bit about how to get users into your app.
Once there, many of them are going to have to log in to your service, for it to be useful.
If that happens, they might see a screen like this, a log-in form.
This is a frustrating moment for your users.
They probably don't remember their credentials even though they might have logged into your site many times in Safari.
They don't want, or don't know, how to look their passwords up.
This is precisely the moment when the user might just abandon ship, leave your app, maybe even delete it.
But what if instead you could present a list of credentials for the user to select?
That would turn this into this.
A simple picker that allows the user to choose from passwords they have previously saved in Safari.
Well, the technology to do this was introduced in iOS 8 and it's called Shared Web Credentials.
Shared Web Credentials allow your app to access Safari's saved passwords and thereby eliminate the headache for users, of r having to remember their passwords in different contexts on different devices.
How does this work?
Well, when Safari on either iOS, or OS X sees a user logging in, or filling out an account creation form, it can help the user in several different ways.
Specifically, Safari can offer to save passwords on the user's behalf in the keychain.
It can sync those passwords to all of the users' devices using iCloud Keychain, so they are always available on the user's iPhone, iPad, and Macs.
It can suggest cryptographically-strong passwords for the user, and of course, it can autofill those passwords on the user's behalf, when the user goes to log in.
And with the Shared Web Credentials API, it can also prevent users from being stuck on your app when they go to log in.
To adopt Shared Web Credentials, there are two parts.
First, you need to establish a tight association between your app and your website just like we talked about for universal links.
Second, you do need to adopt a little API in your app, to access the credentials themselves.
So since shared web credentials allows the user to share their passwords, we need to establish that you control both your app and your website.
Just like we did when we allowed your app to handle links to your website.
Your server tells us to trust your app by a simple addition to the apple-app-site-association file that Jonathan discussed.
Specifically, all you need to do is add a top level dictionary under the web credentials key, with a key of apps, and an array of app IDs, that your server, your website trusts to share credentials.
Your app also needs to tell us to trust your website.
And this is accomplished by adding, a, one or more web credentials entries to the associated-domains entitlement that we talked about earlier.
And you can also do this using XCode's project inspector as Jonathan showed in the demo.
Okay. So now let's talk about adopting the API in your app.
A typical flow in your app might look something like this.
First your app will check to see if it has credentials saved in the keychain, and if so, use them to log in.
If not, it will ask to access any passwords that Safari has saved.
If those are available, it will use those, it will save them locally and then log in.
If none are available, it will present its built-in log-in UI, to ask for credentials and then proceed in the usual manner.
So a basic implementation of requesting Safari's passwords might look something like this.
The key function is SecRequestSharedWebCredential which takes a completion handler, and optionally takes domain and user name arguments.
In the completion handler, we check whether we got credentials back, and if so, use them to log in.
Otherwise, we will present the log-in UI.
Note that in both cases we dispatch to the main queue because there is no guarantee that the completion handler is called on the main queue.
So if your app allows the user to create an account or change their password, you really should update Safari's keychain so that the next time the user goes to log in to your website on, say, their Mac, the correct password is available to be autofilled, and you can do that using the SecAddSharedWebCredential function as shown here.
The Shared Web Credentials API also lets you generate secure passwords using the exact same algorithm as Safari, using the Sec Create Shared Web Credential Password function, as shown here.
There are some additional advanced features of the API, and if you are interested in those, I will refer you to last year's session, Your App, Your Website, and Safari.
So we have talked about some great tools for giving your users the best experience in both your app and your website.
First, on iOS 9 with just a little bit of work on your part, regular links to your website can become universal links.
And can open your app just as easily as they open your website.
And iOS will insure that your users are given the best possible experience, which may be your app, or may be your website.
The days of using custom URL schemes to route users into your app are over.
Next, you can adopt Smart App Banners, which will, the first time your user visits your website, will help them discover your app.
On a subsequent visits, they will help redirect users into your app.
And at all times, they will help App Search index your app and its content, so that it is available to your users in both Safari and Spotlight.
Finally, by adopting shared web credentials, you can give your app access to Safari's saved passwords, and make logging into your app a breeze for your users.
For more information on what we have talked about today, you can check out our documentation.
You can ask a question on the Dev forums, and of course reach out to DTS.
You can also contact John Davis, our Web Technologies Evangelist or Jake Behrens, the App Frameworks Evangelist.
There are several related sessions, both this year and last, that go more into detail on some of the topics we discussed.
Thank you and have a great rest of the conference.
[ Applause ]