A collection of issues with iOS 8 share extensions, along with radar links, sample projects, and workarounds
iOS app extensions – launching this Wednesday, as part of iOS 8 – provide an exciting opportunity for developers of all types of apps to integrate with their customers’ devices like never before. Here at Tumblr, we’re thrilled to pull the curtain off of our share extension, which we’ve been working hard on for quite a while now.
The process of building the Tumblr share extension has been fun, but also really frustrating at times. We’ve hit quite a few problems that we ended up needing to work around, and in the interest of helping you do the same, would like to detail all of the issues that we encountered.
Of course, your mileage may vary with some or all of these. We’ve talked to other developers who haven’t had the same problems, or have hit some that we haven’t. To make it easy to track updates to these problems, we’ve created an issue for each one. Please create pull requests if you’ve got solutions or workarounds, or issues if you’ve encountered something that we didn’t.
UPDATE: This has potentially been fixed.
Apple’s App Extension Programming Guide contains a section on performing uploads and downloads, complete with sample code indicating how background sessions are to be used to perform uploads that may last longer than your extension process is alive for. Normally, anshared container, a special, secure location on disk that both extension and app are able to read and write from. The extension writes a file to the shared container and initiates a task to upload that file. The upload ostensibly occurs in a third process, allowing it to continue even once the extension has been terminated. The container application will then later be woken up and notified as to its success or failure.
We have not been able to get this to actually work.
In our experience, while our extension and container application can both access the shared container without issue, the
NSURLSessionTaskis seemingly unable to. Instead, it spits out errors that you can find in the radar.
As soon as a user taps the “Post” button, we’d ideally like to dismiss the extension and let them get on with their day, while continuing to upload in the background. Given that we haven’t been able to get this to work, we’ve given our extension a progress bar and are keeping it on screen until the request completes. It’s possible that the user could background the host application, and iOS could kill it in order to reclaim the memory, but this seems like our best option given these limitations. We’ll happily go back to using background sessions if the issue we’re seeing ends up getting fixed.
As mentioned, the shared container is where everything that you need to access from both your app and extension must be located: user defaults, keychains, databases, files that you’re serializing via
For existing apps, the problem is simple; the data already exists somewhere outside of the shared container, and only the container app can migrate it over. Thus, if the user installs an update that adds an extension, and tries to use the extension before launching the application and giving it a chance to perform the migration, they’re going to have a bad time.
There’s no great option here. If the user opens our extension first, we just throw up a dialog telling them that they need to launch the application first. Inelegant but necessary.
NSUserDefaultsand SQLite are useful for synchronizing data access across both extension and container application, but as per WWDC Session 217,
NSFileCoordinatoris also supposed to be an option for those of us using
NSCodingfor custom data persistence. We tried hard, but couldn’t actually get it to reliably work.
Our use case required both our app and extension to write to the same file, where only the app would read from it. We observed a number of problems while both extension and app processes were running simultaneously.
relinquishPresentedItemToWriter:) would either:
relinquishPresentedItemToReader:) was called first
Rather than trying to keep access to a single file synchronized across processes, we modified our extension to instead atomically write individual files, which are never modified, into a directory that the application reads from.
This isn’t to say that
NSFileCoordinatorisn’t currently a viable option if you’ve got a different usage than we do. The New York Times app, for example, is successfully using
NSFileCoordinatorin a simpler setup, where the container app is write-only and the extension is read-only.
The Tumblr share extension – like its container application – has a dark blue background color. White looks great on dark blue. Black, not so much.
None so far. Neither Info.plist keys nor view controller methods worked, and we couldn’t even get a handle to the keyboard window the way that applications can usually accomplish using private API (Sam Giddins nearly went insane trying. Thanks Sam!). Here’s hoping for a way to do this in iOS 8.1.
It makes sense that you can’t specifically exclude a specific share extension from an activity view controller. We wouldn’t want Instagram doing something like preventing sharing to Twitter, would we?
But the one extension that you should be able to remove from your own app’s activity view controllers is your own extension. It’s silly to be able to share to Tumblr from within Tumblr. I mean, it works. It’s OK, I guess. But it’s weird.
None so far. We tried configuring our activity controllers with an activity item with a custom UTI, and then specifically giving our share extension a predicate that would cause it to not show up when said UTI was present, but it had unintended side effects, which brings us to the next issue…
This is a doozy. It’s the most important issue we’ve found, and one that probably deserves a blog post of its own.
Here’s how applications pass data to share extensions:
UIActivityViewControllerwith an array of “activity items”
Here’s how we think this should work, using the Tumblr app as an example:
What actually happens is that only share extensions that explicitly support images and URLs and text will show up.
This is a problem, because the simplest way to specify what your extension supports – and by far the best documented – is by adding
`NSExtensionActivationSupportsText` : `YES`
This looks like it would mean “show my extension as long as any of the activity items are text,” but it really means “show my extension as long as there is only one activity item, and it is text.”
Federico Viticci, who at this point has likely used more third-party share extensions than anyone else on the planet, verifies that this is in fact a legitimate problem:
@irace Yup. Been talking to devs to handle exceptions when possible, but I'm getting a lot of failures in several apps.— Federico Viticci (@viticci) September 5, 2014
@irace Yep. And, that the input passed by an app doesn't match what another app's extension expects and you get all sorts of weird stuff.— Federico Viticci (@viticci) September 5, 2014
This negatively affects both app and extension developers. It means that:
App developers should only configure their activity controllers with a single activity item. There are a couple of problems with this. First, it’s doable, but a pain if, like in Tumblr.app, you want system activities like copying and saving to the Camera Roll to support multiple different types of data. Secondly, it’s a huge shame to only export one type of data and limit the number of sharing options that your users will be able to perform.
Extension developers should use the more complex (and unfortunately, not very thoroughly documented) predicate syntax to specifically specify an OR relationship. This would look something like:
SUBQUERY(extensionItems, $extensionItem, SUBQUERY($extensionItem.attachments, $attachment, ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image")[email protected] <= 10)[email protected] >= 1 OR
SUBQUERY(extensionItems, $extensionItem, SUBQUERY($extensionItem.attachments, $attachment, ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text")[email protected] >= 1)[email protected] >= 1
A huge thanks to Matt Bischoff, Paul Rehkugler, Brian Michel, and Sam Giddins for not only helping find these issues and employ these workarounds, but for filing radars, creating sample projects, and helping edit this post as well.
And of course, to the frameworks and developer evangelist teams at Apple. With extensions, you’ve given us a prime opportunity to delight our users even more. We’ve got lots more ideas and can’t wait to see what everyone else comes up with as well.