Apple has had the notion of universal apps since the iPad was first introduced. While the iPad and iPhone may need (and often do need) user experiences that are unique to the device, having both devices running the same operating system means that developers could write their code once and have it run on both devices within a single app bundle.
I wanted to extend this idea of the universal app and create an example that ran on both iOS and tvOS with a unified code base. The app needed to scale from an iPhone all the way up to an AppleTV. While the iPhone/iPad run a different operating system than the AppleTV, tvOS is based on iOS and uses many of the same frameworks and API. To the extent that made sense, I wanted the same code to run on all devices. Where the platforms differed, both in terms of frameworks and API differences as well as user experience idioms then, and only then, would having code distinct to the platform be allowed.
As an example project, we will use Transport for London (TfL) API to get real-time data about the status of various transport lines. In the demo the user can access live feeds of the current status of different lines across Transport for London's network.
Organising the Code – Three Options
There are three different ways to organize a code base in Xcode for creating an iOS and tvOS app with a unified code base, though there are variations on each of these.
- Have two `project` in a `workspace`, one for each platform. Then have a folder for source files in common. When you add an item to it, it then needs to be added to each project.
- Have a single `project` with a `target` for each platform. Again, have a folder for source files in common. When you add an item to it, check the boxes to have it added to both targets.
- Have a single `project` with a `dynamic framework` target for common code and a target for each platform.
In this option, we have two `project` in a `workspace`, one for each platform. Then have a folder for source files in common. When you add an item to it, it then needs to be added to each project.
While this approach may be a developer's first instinct, it is prone to problems. Every time you add a file to one project, you have to remember to do it for the other. It is a multi-click process. It is both tedious and would cause problems if you forgot to add the file to one of the projects.
In Option 2, we have a single `project` with a `target` for each platform. Again, have a folder for source files in common. When you add an item to it, check the boxes to have it added to both targets.
This is a major improvement to Option 1 as you can add a file to both targets in one fell swoop.
In the last option we have a single `project` with a `dynamic framework` target for common code and a target for each platform. This is a great option, especially if you are planning to include Mac OS as a future platform. Take a look at Apple's Lister sample code project for an example on how to do this.
It is significantly more complicated in terms of project and file organisation. Since frameworks are platform specific when compiled, you need two targets (one for iOS and one for tvOS) for the framework. So you have 4 targets in total: iOS app, tvOS app, iOS framework, and tvOS framework. You also need to make sure that when you add a file to the project, you remember to select all the appropriate targets. This is not even counting any targets for Unit or UI tests!
There are also some files that should not be included in the framework targets but that could be universal between the platforms. These include the `AppDelegate`. Using this option, there is no good place to put them.
What Did We Do?
We chose to go with Option 2. This is simpler to manage than Option 3, allows us to reuse even more code, and we have no intention at this time of deploying to Mac OS. It is much less prone to errors than Option 1. We also found major issues getting CocoaPods to install correctly when using Option 1.
When looking at our project folder, you will see three folders for source code. We have separated the shared code, the iOS specific code, and the tvOS specific code. This makes it really easy to keep the project organized for the multiple targets.
Most of the time, having separate files was not needed. Often, using an
#if os(...) was sufficient since there was only a line or two difference between the platforms.
Aside from assets (such as app icons) and the `Info.plist` files, there was only one file that was truly unique between the two platforms.
Major Differences Between Platforms
There are many posts out there describing the minutiae of development differences between iOS and tvOS. We encountered some new ones that have not been as well described.
On iOS, there are two parts to implementing search. You need the original view controller that is used to initiate the search. This may also include the full, unfiltered list of items. Secondly, you need a results view controller that shows the results of the search. While these can be the same view controller, this would break VoiceOver support for your users see StackOverflow for more details.
On tvOS, search is a full screen endeavor. You often jump straight to the results view controller (see Apple's UIKit Catalog (tvOS): Creating and Customizing UIKit) sample code and present it modally.
For most of our table views, this distinction was not an issue. We would present our usual table of items and then present the search if the user clicks on the search bar (or search button in the case of tvOS). However, in the case of the universal search tool, the original view controller had no meaning as there was no unfiltered list of items. Thus on tvOS, we jump straight to the universal search controller. Whereas on iOS, we present an empty table view with a search box at the top first. Since this empty container on iOS is not needed on tvOS it was kept as a separate file for only the iOS target.
Minor Differences Between Platforms
Split View Controllers
On iOS, the bar button to maximize the detail view controller (i.e. the `displayModeButtonItem`) can supplement the usual back button in a navigation controller. That way both can be displayed. On tvOS, this option does not exist, since back buttons do not exist (as they are replaced by the Menu button on the remote).
As outlined in previous sections, there are some major differences in behavior when dealing with search controllers. On iOS, we presented the search bars inline with the table view and updated results within the master controller section of the app. On tvOS, because search must displayed full screen we present this modally to the user. Thus we added a bar button only on tvOS to initiate the search.
Because of those same differences, there is no need to dim the navigation bar when searching on tvOS. Hence this property is unique to iOS.
On tvOS, the bar button items change appearance like many other controls when you focus on them. It switches from a semi-transparent background to white and it grows larger. If you instantiated the bar button item using a title or one of the system buttons that has a text-based representation, the text color becomes black (as opposed to the usual white).
If, instead, you instantiated the bar button from a system button that has an icon-based representation or you are using custom image, the tint color does not become black on focus. Thus your white icon becomes lost in the white background on focus, confusing the user. (Radar: 24363372).
Platform Specific Interfaces
To take a closer look at developing platform specific interfaces beyond a simple
#if os(...) pattern, we have added an additional view to the line detail on tvOS. You can click on an About button and bring up the top of the Wikipedia article for that transport line (this is currently only available for the London Underground).
Rather than using inheritance and having a platform based if statement to determine which view controller to load to display the About button or not, we can add the button using a class extension that only exists on tvOS. This allows us to not have issues with future inheritance hierarchies and leaves us the maximum flexibility going forward.
Unfortunately, as of Swift 2.1 you cannot add a selector for a function that exists in an extension of a subclass of a generic superclass (which we have!). Thus, the About button action needs to be added to the original `LineDetailViewController` definition with an
#if rather than placing it in the extension for better separation of platform specific code. (Radar: 24454686).
Simply having a unified code base should not restrict you to designing platform native experiences. It is still important that the app is a joy to use no matter what the device. In this example we were able to scale nicely with minimal code differences. In a larger app, this might not be the case.
The great thing about our choice in organising the project is that it is very easy to create views and controllers that are unique to the platform. You can even give them the same name and then keep your files in common unaware of the differences in implementation of the platform specific elements.
There are a few other platform specific design ideas I would like to highlight after having completed this exploration. None of these should be taken as law, but they should be used to help you think about making your app's experience fantastic on all devices.
On iOS, having buttons in a navigation bar is a common method for navigating through an app. On tvOS there are no back buttons. When you study Apple's own tvOS apps and other apps popular on the AppleTV, you will find few (if any) that use buttons in the navigation bar. How the user journeys within your app may need have variations based on the platform.
On iOS, tables and collections are the two most common ways of displaying a group of similar items to the user for interaction. On tvOS, the use of tables in a full screen mode tends to be excessive. Imaging a table cell with a few words of text spanning the entire width of a 70 inch television! Instead, collections are favoured on the AppleTV. Their rich content experience is powerful on the larger screen. Tables tend only to be used in a split screen experience, though even then collections can often make sense.
Having beautiful media within your app is vital on any device. It becomes even more important on the AppleTV. Few apps on the iPhone or iPad use images in the background (at least since the advent of iOS 7 and the new design paradigm). On the AppleTV, while skeuomorphism has not returned, having high resolutions images or even video in the background can really make the app shine.
On iOS it is easy to move your finger or stylus to tap or swipe on the right area of the screen to interact with the app. On tvOS where all interaction takes place through a remote or game controller, it is critical that the user be able to quickly navigate between different interactable areas of the screen. Using the default focus guides that link one UI element to nearby elements may not be sufficient. At the same time, when you add additional focus guides it is important that they be clear to the user.
Even when holding an iOS device in landscape, most user experiences tend to move content vertically along the screen. A television is almost always hung in landscape and, in the this day and age of wide (and ultra-wide) screens, having horizontally moving content becomes more natural. It allows more content to flow across a larger portion of the screen.
The example project we used throughout this blog post and our exploration is available on Github.
If you'd like to discuss anything you've read in this post, or talk to us about a potential project, drop us a line at email@example.com. You can sign up to our newsletter on this page below too.
– App Programming Guide for tvOS: Apple's overview for developing for tvOS.– iOS 9.1 to tvOS 9 API Diffs: Apple's list of which frameworks are available on iOS vs tvOS.– Sharing Code Between iOS and OS X: Apple's WWDC 2014 video which looks at some of the techniques the iWork team used to maximize code reuse and project organisation when developing for multiple platforms.– AppleTV Human Interface Guidelines and iOS Human Interface Guidelines: Apple's design and user experience documents for creating great apps that feel native on the platform.