Handling Universal Links in your app

With all parts in place, you can now set your app up to handle Universal Links. In the previous sections of this chapter, a lot of work was done to handle user activities nicely. The setup you created for this will serve as a foundation for handling Universal Links.

Whenever an application is expected to open a Universal Link, the application(_:open:options:) method is called on AppDelegate. This method receives the URL it's expected to open. If the URL can't be opened, this method is expected to return false. If this method does manage to handle the URL, it should return true.

Any application that handles Universal Links must have the Associated Domains capability enabled in the Capabilities tab. To do this, go to the Capabilities tab in your project settings and allow Associated Domains. For every domain that you want to handle Universal Links for, you need to create a new applinks: entry. The following screenshot shows an example configuration:

When the application(_:open:options:) you are expected to map the URL is passed to this method to a view in the application and take the user to that specific view. This process is called routing, and it's a well-known technique throughout programming languages and platforms.

Let's assume that your application received the following URL:

https://www.familymoviesapp.com/familymember/dylan/ 

Just by looking at this URL, you probably have a vague idea of what should happen if an application is asked to open this URL. The user should be taken to a family member screen that shows the detail page for Dylan.

When you receive a URL such as this in AppDelegate, three separate parts exist on the URL. One is the scheme; this property tells you which URL scheme, for example https://, was used to navigate to the app. Usually, the scheme isn't relevant to your app unless you're handling multiple custom URL schemes. Second, there is the host, for example example.com. This property describes the domain that the URL belongs to. Again, this is usually not relevant unless your app handles links from multiple hosts. Finally, there is the pathComponents property. pathComponents is an array of components that are found in the path for the URL. Printing pathComponents for the example URL gives the following output:

["/", "familymember", "dylan"]

The first component can usually be ignored because it's just /. The second and third components are a lot more interesting. They tell you more about the route that needs to be resolved. Going back to the MustC example, you could handle URLs in that app easily with the code that already exists in AppDelegate to navigate to family member and movie pages. The following code shows how:

func application(_ app: UIApplication, open URL: URL, options:
  [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
  let pathComponents = URL.pathComponents
  guard pathComponents.count == 3
    else { return false }

  switch(pathComponents[1], pathComponents[2]) {
  case ("familymember", let name):
    return handleOpenFamilyMemberDetail(withName: name)
  case ("movie", let name):
    return handleOpenMovieDetail(withName: name)
  default:
    return false
  }
}

A switch with powerful pattern-matching is used to check whether the second component in the URL points to a family member or a movie and we parse the third component into a variable called name. If one of these matches, we call the existing methods; if nothing matches, we return false to indicate that we couldn't open the URL.

Earlier, you saw this URL: https://www.donnysrecipes.com/search/?q=macaroni. With pathComponents, you can easily gain access to the path of the URL. But how do you get the final part of the URL? Well, that's a little harder. You can get the query property for a URL, but then you get a single string in the form of q=macaroni. What you want is a dictionary where q is a key and macaroni is a value. The following extension on URL implements a naive method to create such a dictionary:

extension URL {
  var queryDict: [String: String]? {
    guard let pairs = query?.components(separatedBy: "&")
      else { return nil }

    var dict = [String: String]()

    for pair in pairs {
      let components = pair.components(separatedBy: "=")
      dict[components[0]] = components[1]
    }

    return dict
  }
}

First, the query string is retrieved. Then the string is separated on the & character because multiple key-value pairs in the query string are expected to be separated with that character. The code loops over the resulting array and separates each string on the = character. The first item in the resulting array is used as the dictionary key and the second item is the value. Each key and value are added to a dictionary, and finally, that dictionary is returned. This allows you to get the value for q, shown as follows:

URL.queryDict!["q"] 

Great job! This is all that's needed to implement Universal Links in your app and to enhance discoverability for your users.