Troubleshooting Handoff.

My iOS app was advertising its Handoff activity to its Mac counterpart just fine. The reverse just would not happen. 11 back and forth TSI emails later, we figured it out. Now I feel like sharing!

Step #0: Debug Build

Just do a normal Debug build. There’s no need to use Release, pick distribution identities, exporting, etc. to enable Handoff.

Step #1: Activity Types

This is trivial, and you probably have this down already: both versions must use the same activityType, and they both must have a NSUserActivityTypes array key in Info.plist, containing that same activityType string.

Step #2: Sandbox (OS X)

Your Mac app must be sandboxed. Turn on App Sandbox in the Capabilities tab of your OS X target.

Step #3: No Developer ID (OS X)

Here’s a warning, straight from the mothership:

It is a known bug that Developer-Id signed apps do not work with Handoff.

You heard the man: do not sign with Developer ID.

Step #4: Code Signing Identity

Instead, for your Debug builds, sign with an App Store development certificate, a.k.a. “identity”.

On OS X, those certificates’ names start with Mac Developer: ....

On iOS you’re probably OK, but just in case, use one that starts with iOS Development: ....

Step #5: Provisioning Profile

Both apps [must be] signed by the same team ID

This is where I had it wrong.

Apparently, Team IDs for development certificates are, er… random? But that’s okay. What seems to count (for Handoff, at least) is the Team ID from the provisioning profile used to sign the app. Or more precisely, the corresponding entitlement that is generated.

You can use this command to verify that the entitlement is present:

codesign -dvv --entitlements - yourapp.app

You’re looking for the com.apple.developer.team-identifier entitlement:

<key>com.apple.developer.team-identifier</key>
<string>YOUR-TEAM-ID</string>

Make sure it’s there, and that the Team ID it shows is correct.

If it’s missing or incorrect, I suggest you manually select a Provisioning Profile in your build settings. In my case, automatic provisioning profile selection was failing - possibly because of the random Team IDs (?).

Step #6: If Everything Fails (OS X)

Regenerate your development certificate and provisioning profile, and then select them in your build settings. Apparently, some old ones are busted in some mysterious way.

Bonus #1 (OS X)

If you’re having trouble closing windows in your app in Release builds, or seeing this in Console:

Cannot update for observer <NSUIActivityResponderMonitor 0x60800000a230> for the key path “mainWindow.firstResponder” from <NSApplication 0x6000001181e0>, most likely because the value for the key “mainWindow” has changed without an appropriate KVO notification being sent. Check the KVO-compliance of the NSApplication class.

You can work around this by avoiding the userActivity property of NSResponder. Instead, create and keep around your own NSUserActivity:

let handoffActivity = NSUserActivity(activityType: "com.example.app.activity")

And manually call becomeCurrent() on it when appropriate. You can still use the delegate/needsSave mechanism.

handoffActivity.needsSave = true
handoffActivity.delegate = self
handoffActivity.becomeCurrent()

Not as elegant as the responder chain way, but it works.

I don’t know what’s causing this, but I’ve filed Radar #19138455. I’ll update this article when the issue is resolved.

Bonus #2 (OS X)

You can get more Console logging from sharingd (the deamon responsible for Handoff, among other things), by restarting it after setting this sneaky hidden preference flag:

defaults write com.apple.Sharing EnableDebugLogging -bool TRUE

Hope this helps! Let me know if you’re still having problems.