For this, the inaugural post from an iOS team member here at Bloglovin’, I figured: why write yet another look-at-how-clever-we-are tech post? You can't throw a rock on the Internet without it knocking against some #humblebrag engineering blog. Instead, I'm going to write about something really dumb I did, and what I learned from it. Because:
Defeat is more entertaining than victory. Or whatever. ~ Splinter the Rat.
Hopefully this will help you avoid the same mistakes and misunderstandings when updating your apps for iOS 9.
First, a bit of background on URL schemes in iOS 9:
As you may have discovered already, iOS 9 establishes barriers around the use of URL schemes, roping off what was previously open territory. An app's .plist must now register the URL schemes for other apps it wishes to open, not just its own URL scheme(s). Ostensibly, this is a counteraction to Twitter's unscrupulous use of
[UIApplication canOpenURL:] to analyze which apps a user has installed on her device. Once an app links against iOS 9,
canOpenURL: will always fail for any URL scheme not listed in the host app's .plist, even if the target app is actually installed. Bizarrely, calling
openURL: on that same URL scheme will successfully open the target app — but only up to fifty times. Updating the .plist removes this cap.
Bloglovin’ for iOS allows users to signup or login via their Facebook accounts. We accomplish this via the Facebook iOS SDK. There's a straightforward login method in the SDK whose implementation uses
canOpenURL: to query for a compatible Facebook app that can handle the login request. If a compatible app cannot be found, the SDK falls back to opening
facebook.com in Safari.app instead.
I knew most of this when I set out to update Bloglovin’ for iOS 9 compatibility. But I ran into bugs that made me doubt my ability to fix them, and even made me doubt my sanity, until I realized I was trying to fix a bug I had already solved.
Steps to Reproduce
I launched a development build of Bloglovin’ on my recently-upgraded iOS 9 test device. From the Bloglovin’ start up screen I tapped the "Connect to Facebook" button, and saw this alert from the OS:
I accepted the alert and was surprised by the result. Instead of being catapulted to Facebook.app for authentication, I remained in the Bloglovin’ app. The Facebook SDK returned a "user cancelled" result, which usually happens in response to one of two things:
Facebook SDK Cancellation Basis Paths:
- The user taps the "Cancel" button in the Facebook.app authentication modal, bouncing back to the host app.
- The user presses the home button while viewing Facebook.app authentication modal, and returns to the host app manually from the home screen.
Both of those things will lead to a "user cancelled" result. But after accepting the system alert I was never brought to Facebook.app, nor did it fall back to launching Safari.app. Instead I immediately received a user cancellation result from the Facebook SDK.
My first thought was that perhaps the alert view presented by the OS was presented in such a way that application life cycle events were firing on the Bloglovin’ app, which led to the Facebook SDK thinking that I had followed cancellation basis path #2 above. If this is what was happening, then this would be big problem.
I wanted to be sure. I added some logging and some breakpoints to the Bloglovin’ app, and launched the updated debug build on my device. I followed the same basis paths above, only this time there was no URL scheme alert view from the OS about opening Facebook.app. Instead it followed the standard basis path, opening Facebook.app without a hitch.
It seemed that, like other privacy permissions on iOS, the alert for a URL scheme is only shown the first time. Subsequent attempts would be implicitly allowed. If so, and given the bug I thought I had found above, this would mean that the Facebook SDK would need to be updated to anticipate these one-time system alerts and ignore false-positive cancellations during first-time connections on iOS 9. If they didn't update the SDK, then every first-time attempt to connect to Facebook would fail.
I needed to be sure about this, so I attempted to reset the URL scheme permissions and try again, to no avail. I could not get the URL scheme alert view to reappear. I tried deleting Facebook, then deleting both Facebook and Bloglovin’. Then I tried using "Reset Privacy & Location" in the system settings. I tried doing all this while setting the system date forward a week. I even tried a hard reset of the entire device. I simply could not get the system alert view to appear again.
I grew nervous. How could I verify that Facebook connections always work under iOS 9 if this critical edge case was unreproducible?
Lastly I tried running the iOS 9 build on a device that had never run it yet. I hit the hot keys for Build & Run — and it was then that I realized my folly.
Recall above where I wrote "I launched a development build of Bloglovin’ on my recently-upgraded iOS 9 test device." This was a build created with Xcode 6, not Xcode 7. Even though the device was connected to my computer via a USB-to-Lighting cable and Xcode 7 was open and the latest
ios9 branch of Bloglovin’ was checked out in git, I had absent-mindedly launched Bloglovin’ by tapping the app icon on my home screen, rather than using Build & Run in Xcode 7. This meant the app was only linked against the iOS 8 SDK, at least the first time I tried it. All subsequent launches were in response to the Build & Run command, and were thus linked against iOS 9.
Look closely at the screenshot above. Notice something strange? The cancel button is set in a bold weight and occupies the right-hand position, which is typically used for accept buttons. In contrast, look at the system alert view for push notification permissions:
The push notification alerts use the standard button positions. I had relied on muscle-memory to tap what I thought was the accept button for the URL scheme alert, but had actually tapped the cancel button. That’s why the Facebook SDK was reporting a user cancellation result.
You may be trying to reproduce a bug you’ve already fixed. - The reason I could not reproduce the bug was because I had already fixed it! I had updated the .plist with Facebook’s URL schemes on our
ios9branch in git before I ever saw the first alert view on a real device. Authentication via Facebook.app was latently fixed in Bloglovin’ on iOS 9. It was only via absent-minded mistakes that I was able to trigger the unhappy iOS 8 basis path.1
Don’t assume iOS’ permission behaviors are consistent. - iOS 9 only shows URL scheme alert views for host apps linked against iOS 8 or earlier. Once a host app links against iOS 9, it will never prompt the user for permission. You must update the host app's .plist with the URL schemes you need to open. I had assumed that URL scheme alert views would be shown to all users, regardless of whether an app is linked against iOS 9 and has an updated .plist, in much the same way that push notifications always require explicit user permission even if the correct code signing paraphernalia are in place.
Don’t rely on muscle memory when granting iOS permissions. - The URL scheme alert view reverses the standard position of the accept and cancel buttons. Make sure your support staff anticipate this confusion when trouble-shooting "I can’t log in via Facebook" bug reports.
Other helpful lessons learned: the Facebook SDK will not fall back to Safari.app if your customers are running an iOS 8 build of your app on an iOS 9 device and deny permission to launch Facebook.app via its URL scheme. It will simply fail with a "user cancelled" result. The Facebook SDK will fall back to Safari.app if your customers are running an iOS 9 build of your app on an iOS 9 device when your iOS 9 build does not include Facebook’s apps URL schemes it its .plist. ↩