Written by Steve Johnson
Dec 02, 2015

Creating Top Shelf content for your tvOS App

A guide to developing Top Shelf content for a tvOS app

Configuring Top Shelf content

You’ve created a fantastic new tvOS App, either using native code or by adopting Apple’s new TVML format. Now you want to show the world that your content is dynamic and up-to-the-minute.

Fortunately, Apple thought of this, and provide the TV Services Extension, which allows you to configure your Top Shelf items.

As with all development, there are hurdles and unseen dangers, and to this end I’d like to share with you some of the lessons we learned about Top Shelf whilst working on a recent client project.

Assuming you have already created your tvOS app, let’s get right into creating some Top Shelf content.

Add the TV Services extension

The first thing you will need to do is add the TV Services extension to your project.

From Xcode and within your tvOS project, go to the File menu and select New > Target

Under the Application Extensions, select TV Services Extension and tap Next. If you’re stuck for a name, just call it TopShelf.

Inside this extension, you will find ServiceProvider.m, and within this two methods; topShelfStyle and topShelfItems. This is where the magic happens.

It’s all about style

Let’s start with topShelfStyle, of which you have two styles to choose from:


The iTunes app uses this style, with sections titled Purchased and Top Movies, each of which containing a subset of titles. This is ideal for situations where you have a lot of dynamic categorised content.

You can either use poster (2:3) or square (1:1) aspect ratios for your images.

The poster should use an image 404 by 608 pixels ensuring your foreground content lies within a central ‘safe’ zone of 380 by 570 pixels. The square aspect should use an image 608 by 608 pixels with a safe zone of 570 by 570 pixels.


The App Store app uses this style; ideal for situations where you want to showcase a smaller number of items.

You should create an image 1940 by 692 pixels ensuring your foreground content lies within a central ‘safe’ zone of 1740 by 620 pixels.

To really add some fit and finish to your content, consider downloading Apple’s Parallax Previewer and building some layered images; the results are truly stunning when executed well.

You will find the method already exists in ServiceProvider.m to set the topShelfStyle; all you need do is tweak the returned TVTopShelfContentStyle to suit your needs.

Using the Inset Style

This next section is a little more involved, but not rocket science.

Your topShelfItems method should return an array containing TVContentItem objects. Depending on your chosen style, these items either contain image and deep linking information (inset style) or a title and an array of TVContentItem objects (sectioned style).

Please note, if you set up the wrong hierarchy for the style you have chosen, the display will revert to the TopShelf image you set within the app.

- (NSArray *)topShelfItems
    // Create an array of TVContentItems.
    TVContentIdentifier *identifier = [[TVContentIdentifier alloc] initWithIdentifier:@“MyApp.uniqueIdentifier" container:nil];
    TVContentItem *item = [[TVContentItem alloc] initWithContentIdentifier:identifier];
    item.imageURL = [NSURL URLWithString:@“http://”];
    item.playURL = [NSURL URLWithString:@"MyApp://play/"];
    item.displayURL = [NSURL URLWithString:@"MyApp://display/"];
    return @[item];

Allow Arbitrary Loads

App Transport Security.png

It is important to note at this stage that, thanks to a ‘feature’ introduced from OS9, in order to access http content from within your extension, you need to Allow Arbitrary Loads.

Open your extension’s Info.plist file and add “App Transport Security Settings”, setting “Allow Arbitrary Loads” to YES (as in image above).  

Let’s walk through the topShelfItems method:

TVContentIdentifier *identifier = [[TVContentIdentifier alloc] initWithIdentifier:@“MyApp.uniqueIdentifier" container:nil];

Each item on the top shelf requires a unique identifier; best to prefix this with your app name. As these are top level items, we can set the container to nil.

We then instantiate our first item, initialised with the identifier we just created…

TVContentItem *item = [[TVContentItem alloc] initWithContentIdentifier:identifier];

…and set the properties accordingly:

    item.imageURL = [NSURL URLWithString:@“http://“];
    item.playURL = [NSURL URLWithString:@"MyApp://play/"];
    item.displayURL = [NSURL URLWithString:@“MyApp://display/"];

The only properties we’re interested in for the inset style are imageURL, playURL and displayURL. imageURL contains a URL to your top shelf image (either a 2D image format such as .png or .jpg, or as a parallax .lsr format).

I did try with a pdf image, but this appears not to work. playURL and displayURL are invoked when you press on the play button and trackpad respectively.

Deep Linking into your App

URL Types.png

The playURL and displayURL properties allow you to fire off a deep link which you can intercept in your main app’s AppDelegate class with the following method:

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options

Return the item as an object in an array

In order to make your app respond to the Deep Link URL, you will need to set up the URL type, identifier and Scheme in the Info.plist of your main app (as above).

Return the item as an object in an array

Finally, you return the item as an object in an array:

return @[item];

The process of adding more inset items to your list is to repeat the above process (from creating a unique identifier to setting the content item properties) and returning each item as part of the array; eg:

return @[item1, item2, item3];

Obviously, there is more than one way to skin a cat here, and you may wish to set up each item using remote data from a JSON source; iterating through each node to create the content on the fly, but that’s beyond the scope of this tutorial.

Using the Sectioned Style

As before, we create top-level TVContentItem objects, but this time they are only being used as ‘wrappers’ and as such, only use the title and topShelfItems properties.

    TVContentIdentifier *firstSectionId = [[TVContentIdentifier alloc] initWithIdentifier:@"MyApp.wrapperId1" container:nil];
    TVContentItem *firstSectionItem = [[TVContentItem alloc] initWithContentIdentifier:firstSectionId];

Next we create our first sub item. Note that we are using the top-level firstSectionId as its container.

    TVContentIdentifier *identifier = [[TVContentIdentifier alloc] initWithIdentifier:@“MyApp.list1.uniqueIdentifier” container:firstSectionId];
    TVContentItem *item = [[TVContentItem alloc] initWithContentIdentifier:identifier];
    item.title = @“First Content Item”;
    item.imageURL = [NSURL URLWithString:@“http://”];
    item.playURL = [NSURL URLWithString:@"MyApp://play/"];
    item.displayURL = [NSURL URLWithString:@"MyApp://display/"];
    item.imageShape = TVContentItemImageShapePoster;

As before, we set up our image and deep linking information.

In the Sectioned style, the title property is used, and we can also set an imageShape.

You can choose between None, Poster, SDTV, Square, Wide, HDTV and ExtraWide based on your content.

Finally, we set up the wrapper’s title and topShelfItems array and return the final array.

firstSectionItem.title = @“First Section”;
firstSectionItem.topShelfItems = @[item];

return @[firstSectionItem];

Once again, you may wish to import your content information remotely and you will most certainly want to structure your code in a less ‘boilerplate’ fashion once you’ve become familiar with the TVContentItem class. I’ll leave that up to you.

Notifying changes

Whenever you update the content you are displaying on your top shelf, you will need to send a TVTopShelfItemsDidChangeNotification via the default notification center as below.

You can post this within your app or within the extension; in the former instance, you will need to import <TVServices/TVServices.h>. This notification will prompt the Apple TV to update your top shelf content at the next available opportunity, though there are no specifications regarding timescales.

[[NSNotificationCenter defaultCenter] postNotificationName:TVTopShelfItemsDidChangeNotification object:nil];

That’s basically it.

Your top shelf content is ready to roll; you just need to convince your users to install your app on their top shelf, otherwise they won’t benefit from all your hard work.


Further resources

Apple’s own documentation can be found here:


tvOS Developer resources (including a download link for the Parallax Previewer):