What we learned building the Tumblr iOS share extension

· 8 minute read

A banner at WWDC showing Tumblr’s share extension. Guess we had to build one…

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 and share workarounds, we’ve created an issue in this GitHub repo for each one. Please create pull requests if you’ve got solutions, or issues if you’ve encountered something that we didn’t.

We couldn’t get background file uploads to work

Radar #18107172: Background NSURLSessionUploadTask cannot read file in app group shared container (sample project)

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, an NSURLSessionUploadTask can be created from a stream, raw data, or a file URL, but only the latter is intended to be used in an extension. This makes sense: communication between extensions and container applications in the same “app group” occurs through a shared 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 NSURLSessionTask is seemingly unable to. Instead, it spits out errors that you can find in the radar.

Workaround

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.

The container application must be opened before the share extension can be used

Radar #18119318: Need a way to migrate data into a shared container without requiring the user to explicitly launching the containing app before using the extension

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 NSCoding, etc.

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.

Workaround

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.

We couldn’t get NSFileCoordinator to work

Radar #18341292: NSFileCoordinator does not work reliably across applications and share extensions

NSUserDefaults and SQLite are useful for synchronizing data access across both extension and container application, but as per WWDC Session 217, NSFileCoordinator is also supposed to be an option for those of us using NSCoding for 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. NSFilePresenter methods intended to indicate that the file had been or will be modified (presentedItemDidChange or relinquishPresentedItemToWriter:) would either:

Workaround

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 NSFileCoordinator isn’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 NSFileCoordinator in a simpler setup, where the container app is write-only and the extension is read-only.

Share extensions can’t set the status bar color

Radar #17916449: Share extension status bars don’t respect preferredStatusBarStyle (sample project)

The Tumblr share extension – like its container application – has a dark blue background color. White looks great on dark blue. Black, not so much.

We tried everything, but couldn’t find a way for our share extension (which uses a custom view controller subclass, as opposed to SLComposeServiceViewController) to specify its status bar style. Instead, we always get the status bar style of the host application. Since we’re expecting Photos.app and Safari – both which have black status bars – to be two of the apps that Tumblr users share from the most, this is really disappointing.

Workaround

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.

You can’t exclude your own share extension from your application’s activity controllers

Radar #18065047: There’s no way to exclude your own app’s share extension from showing up within the app

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.

Workaround

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…

By default, share extensions will only show up if they explicitly support all of the provided activity items

Radar #18342403: NSExtensionActivationRules should only need to match a single activity item for a share extension to be displayed Radar #18150467: Documentation for custom NSExtensionItemActivation rules is very vague

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:

  • An application configures a UIActivityViewController with an array of “activity items”
  • The activity controller displays the system activities and share extensions that can operate on the types of items provided

Here’s how we think this should work, using the Tumblr app as an example:

  • The user long-presses on a photo
  • We put the image data, the posts’s URL, and maybe a text summary of the post, all in the activity items array
  • We’d expect share extensions that support either image data or URLs or text to all show up in the activity controller

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 NSExtensionActivationRule keys like:

`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:

Workaround

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").@count = 1 OR SUBQUERY(extensionItems, $extensionItem, SUBQUERY($extensionItem.attachments, $attachment, ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text").@count >= 1).@count >= 1

Misc.

Radar #18207630: Table view content insets get adjusted wildly when rotating a share extension (sample project). Minor, especially relative to the rest of these issues, but we’re already over 2,000 words here. What’s a few more?

Thanks!

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.


Originally published on Tumblr

Background check: multitasking on iOS throughout the years

· 4 minute read

“What are you doing that for?”

It’s 2012. Imagine that you’re an iOS developer who understands how the operating system manages which apps are running at any given time. You happen to notice that your friend has opened the app switcher on their iPhone and is manually “killing” each application, one by one.

They tell you that they’re doing this to increase their battery life, or to make their phone faster. Even worse, they might have been told to do this by an Apple store Genius Bar employee. It’s not surprising that after using a Windows computer for most of their life, they’re inclined to believe that this might actually be true.

You know that it isn’t true though. You try to explain that those applications either aren’t running at all, or are in a suspended state that doesn’t impact their phone’s performance or battery life. If iOS needs to free up memory, it’ll do so without them needing to intervene, you say. They resist.

“So you’re telling me that closing all of these will have zero effect on my battery life?”

Sigh. OK, now how are you going to explain this?

“Well, for the most part, it won’t. But if one of those apps is tracking your location, or playing music, for example, then it might help. But in most of those cases, you probably don’t want to kill them, right?”

Multitasking on iOS has changed a lot, but it’s always aimed to be something that the user doesn’t need to understand. Though present in Android from the start, third-party iOS applications weren’t allowed to do any background processing until iOS 4 was released in 2010. Even then, the permitted backgrounding behaviors were quite limiting. Specifically, apps could:

  • Play audio or video
  • Continue a task that was already in progress (for up to approximately ten minutes)
  • Monitor the user’s location
  • Receive incoming Voice over IP (VoIP) calls

When iOS 5 came out, it added the ability for external accessories to communicate with backgrounded applications (e.g. over Bluetooth), as well as for Newsstand applications to download new content once per day.

Apple believes that providing great performance and battery life is more important than providing full-blown multitasking capabilities. Despite iOS 4 providing a reasonable compromise, many developers felt hamstrung by the limited capabilities. While developing Instapaper, Marco Arment proposed that more generic multitasking be permitted under conditions that wouldn’t noticeably impact the user. Tapbots tried playing a silent audio clip but were rejected during App Review. Eventually, News.me figured out that Apple would turn a blind eye towards using geofencing to trigger content fetches, a hacky yet effective tactic that many other applications also rushed to adopt.

And then iOS 7 arrived.

With iOS 7, Apple has changed app multitasking to behave very similarly to how Arment requested three years prior. Applications are now able to specify “background fetch” code that iOS will periodically run, regardless of if the app is currently in use. The interval at which an app will be “woken up” to run in the background depends on how frequently (and when) the user tends to use that particular application, and ostensibly factors such as the device’s current battery level. This system provides most of the benefits of full-blown multitasking while still remaining conservative with regards to battery drain.

Additionally, developers now have the ability to send silent push notifications that can wake up an app and allow it to fetch data or do some other work in the background. Imagine that you use an app to listen to podcasts. When a show that you subscribe to is updated, a server could send a silent push notification to that app on your phone, which could then download the new episode in the background. This is even more battery friendly than using the aforementioned background fetch APIs, as network requests are only made when there’s actually new data available to be retrieved.

Back to your friend.

iOS 7 also comes with a fancy new app switcher that allows you to kill an app with a nice, even fun, upward swipe gesture. And now that applications actually do run in the background, it makes sense that Apple would make force quitting an app a little more user friendly. As such, feel free to let your friend believe that what is now actually true on iOS 7 was true beforehand as well, even though you know that it wasn’t.

With one big caveat.

All of the new multitasking support—normally working its wonders to keep apps updated in the background without draining your battery—comes to a screeching halt for any apps that you’ve swiped out of the app switcher.

Removing an app from that switcher not only terminates any background operations that are currently occurring, but also prevents any that the OS may have permitted in the future. This means that the app won’t be woken up again to perform background fetches, and won’t receive anymore silent push notifications from the server. In order for these new forms of backgrounding to resume, you’ll have to explicitly launch the application again from your home screen.

In summary, iOS 7 makes true what many thought was already the case to begin with: that force quitting apps can help to save scarce resources such as battery. iOS 7 provides new opportunities for developers to provide great user experiences, but as much as Apple would love the inner-workings of iOS to remain “magical,” a cursory understanding of how multitasking affects battery life can truly go a long way.

The next time you see a friend obsessively clearing out their app switcher, make sure they know that while they may be prolonging their battery life, they might not have anything new to read the next time they get on the subway.


Originally published on Finer Things in Tech

Google+ is walking dead

· 2 minute read

Alexia Tsotsis and Matthew Panzarino:

What we’re hearing from multiple sources is that Google+ will no longer be considered a product, but a platform – essentially ending its competition with other social networks like Facebook and Twitter.

Lots of chatter about whether Google+ is really “walking dead” in the wake of news that Vic Gundotra will be leaving the company. I understand that Google+ is less of a singular product and more of a collection of tools, but disagree with The Next Web when the say the following:

[It’s] not a social network, [it’s] a social layer. It’s nothing more than the NYT adding new buttons, commenting systems, and other social tools to its site. Would you call the NYT a “social network”? No, of course not. The Next Web has social features all over the place, but would you call it a “social network”? No, of course not.

When viewing a site with social sharing buttons like the NYT, I’m not forced to have an account page that shows up if somebody Googles for my name, where people can add me to “circles” and try to interact with me in ways that I want nothing to do with. Yes, I know it’s possible to hide your page, but it’s far from obvious how to do so, and really isn’t something I should be forced to figure out in the first place.

YouTube comments were forced to go through Google+. Before it was shut down, Google Reader’s sharing options were replaced with Google+. I once tried to change my YouTube password and was unable to do so without first enabling my Google+ account. This happened again when I first tried to use the (iOS) Hangouts app, where all I wanted to do was continue existing Google Talk conversations that had already been ongoing for years.

Despite what it may have since grown into or even what internal intentions originally were, Google+ was, on the surface, a Facebook clone that was thrust in all of our faces after Google admitted that they missed the boat on social. If the name now also encompasses some really good services (e.g. Google+ Cloud Backup), that’s a marketing problem that Google can only blame themselves for. iCloud is another umbrella term with similar issues. Some parts (backups, Safari tabs and bookmark sync, for example) work great, but as long as others don’t, “iCloud sucks.”

One big change for Google+ is that there will no longer be a policy of “required” Google+ integrations for Google products, something that has become de rigueur for most product updates.

This sounds like a welcome change. I’ve no doubt that the more service-y components of Google+ will be top notch, if they aren’t already, and backing away from a singular social product in order to let them shine sounds like the right move.

But in the meantime, I’m won’t be jumping to embrace Google+ anytime soon. Good engineering and product work will likely be ignored by many potential users as a direct result of how aggressively disrespectful Google was with their initial pass at flooding all of their users’ Internet experiences with, let’s be honest, spam.

Whether or not Google+ actually a massive success internally (I have to assume that it is), the public perception of its failure feels like karma to me.


Originally published on Tumblr