As of iOS 9, third party applications are no longer able to query arbitrary URL schemes. Several third party applications, most notably Twitter, misused the canOpenURL(_:)
method of the UIApplication
class to track which applications are installed on a particular device.
Apple has put a number of restrictions in place to protect the privacy of its customers. In this quick tip, I tell you what you need to know about these changes and I show you how you can update your applications.
1. Querying URL Schemes
I assume you already know that an iOS application can ask the operating system to launch another application using a URL scheme. In its simplest form, it works something like this.
let URLAsString = "tweetbot://_bartjacobs/timeline" if let URL = NSURL.init(string: URLAsString) { UIApplication.sharedApplication().openURL(URL) }
At times, it is useful to first ask the operating system whether a URL can be opened before opening it. This is especially useful if you want to update the user interface based on which applications the user has installed and your application can interact with. In the above example, openURL(_:)
won't succeed if Tweetbot isn't installed on the user's device. To ask the operating system whether a URL can be opened, you can make use of the canOpenURL(_:)
method of the UIApplication
class.
let URLAsString = "tweetbot://_bartjacobs/timeline" if let URL = NSURL.init(string: URLAsString) { if UIApplication.sharedApplication().canOpenURL(URL) { UIApplication.sharedApplication().openURL(URL) } else { print("Cannot Open URL") } }
Unfortunately, some applications, most notably Twitter, have been misusing canOpenURL(_:)
to detect which applications are installed on the user's device. According to Apple, this breaches the user's privacy. As a result, Apple no longer tolerates this type of misuse in iOS 9 by imposing two restrictions.
- Applications built against the iOS 9 SDK are forced to whitelist the URL schemes they would like to query. In other words, a call to
canOpenURL(_:)
fails if the URL isn't added to a whitelist in the application's Info.plist. - Applications not built against the iOS 9 SDK continue to work as expected. There is one limitation, though. An application can only query 50 distinct URL schemes. Subsequent requests return
false
and throw an error. The documentation emphasizes that this limitation is reset when the user reinstalls or upgrades the application.
2. Project Setup
Let me show you what this means in practice by creating a simple application that opens Tweetbot, my favorite Twitter client. Create a new project in Xcode 7 and choose the Single View Application template. Name the project Schemes and set Language to Swift.
Before we take a look at URL schemes, I want to set up the user interface. Open ViewController.swift and add an action, openTweetbot(_:)
, to the ViewController
class. You can leave its implementation empty for now.
// MARK: - Actions @IBAction func openTweetbot(sender: AnyObject) { }
Open Main.storyboard and add a button to the View Controller Scene. Set the title of the button to Open Tweetbot and connect the button with the view controller's openTweetbot(_:)
action we created earlier.
We are not going to make anything too complicated. When I tap the Open Tweetbot button, the operating system opens Tweetbot, showing me my timeline. You can read more about the URL schemes for Tweetbot on the Tapbots website.
3. Before iOS 9
Before Apple imposed the aforementioned restrictions on querying URL schemes, you could do the following:
@IBAction func openTweetbot(sender: AnyObject) { let application = UIApplication.sharedApplication() let URLAsString = "tweetbot://_bartjacobs/timeline" guard let URL = NSURL.init(string: URLAsString) else { return } guard application.canOpenURL(URL) else { return } // Open URL application.openURL(URL) }
We ask the operating system whether it can open the URL we pass to canOpenURL(_:)
. If this method returns false
on iOS 8 and lower, we know the application we are interested in is not installed on the user's device, assuming the URL scheme relates to another application. This can be very useful, but Apple wants to put some restrictions on the API to avoid abuse.
4. iOS 9
If you build the application in Xcode 7 and tap the Open Tweetbot button, canOpenURL(_:)
returns false
and the operating system throws an error that looks something like this:
Schemes[9227:3539016] -canOpenURL: failed for URL: "tweetbot://_bartjacobs/timeline" - error: "This app is not allowed to query for scheme tweetbot"
The operating system explicitly informs us that the application is not allowed to know whether it can open the URL we pass to canOpenURL(_:)
. It doesn't mean that the application is not allowed to open the URL we passed to openURL(_:)
, though.
If you update the implementation of openTweetbot(_:)
as shown below and you have Tweetbot installed, the application is able to open Tweetbot when the button is tapped. This emphasizes that Apple wants to limit the (mis)use of canOpenURL(_:)
, not openURL(_:)
.
@IBAction func openTweetbot(sender: AnyObject) { let application = UIApplication.sharedApplication() let URLAsString = "tweetbot://_bartjacobs/timeline" guard let URL = NSURL.init(string: URLAsString) else { return } // Open URL application.openURL(URL) }
5. Whitelisting URL Schemes
Fortunately, there is an easy solution to this problem. As of iOS 9, Apple asks developers to whitelist the URL schemes an application would like to query. This is as simple as adding an entry to the application's Info.plist.
Open Info.plist, add a key named LSApplicationQueriesSchemes, and set the type of the value to Array. Add an item of type String to the array and set its value to tweetbot.
If we revert openTweetbot(_:)
to its original implementation, we are able to invoke canOpenURL(_:)
without the operating system throwing errors at us. You can add as many URL schemes to your application's Info.plist.
@IBAction func openTweetbot(sender: AnyObject) { let application = UIApplication.sharedApplication() let URLAsString = "tweetbot://_bartjacobs/timeline" guard let URL = NSURL.init(string: URLAsString) else { return } guard application.canOpenURL(URL) else { return } // Open URL application.openURL(URL) }
Conclusion
Most applications won't have a problem with Apple's new policy. It is clear Apple aims to protect the privacy of its customers by limiting the information applications can extract from the operating system. Applications are sandboxed on iOS and Apple wants to control how much information an application can extract from the environment the sandbox lives in.
Whitelisting URL schemes isn't a big pain for most applications, but it can become tedious if you plan to create an application launcher, such as Launch Center Pro. It is still possible to create a launcher as long as you whitelist every URL scheme the application would like to query. This can be pretty tedious, though.