Simplicity is the ultimate sophistication

iOS Newsstand Tutorial

| Comments

A new way to distribute magazines

Since the beginning of the iPad era, one of the most appreciated features in the tablet has been the possibility to read magazines and newspapers. Practically all major publishers (and a lot of minor) have created their own magazine and newspaper apps and the first in-house experiments have grown with increasingly better applications. Besides a lot of software companies followed this path, the major ones by providing their own publishing system and a lot of other start-ups simply exist to offer their services to create custom apps for their customers.

While Apple in the time applied several regulations, e.g. limiting the possibility to purchase issues outside the In App Purchase system, or introducing a subscription mechanism, it behaved before iOS 5 mainly as an observer, limiting its own participation in the app arena with the iBooks app, dedicated to books. But with iOS 5 we assisted to the first important step of Apple towards introducing a coherent methodology for magazine and newspapers applications, by providing a feature, called Newsstand, and a developer a framework, called Newsstand Kit, that provides a common hub for all magazine and newspapers apps and a new completely different way to interact with an application. It is important to highlight the fact that each developer or publisher can still present the content in the preferred way, Apple currently doesn’t provide any specific solution for content presentation, apart the generic HIG guidelines and of course the moltitude of services aimed to present content (PDF, images, media). We will not be surprised if in the next future Apple will change its SDK rules by requiring all apps to be Newsstand compatible to be eligible for the App Store.

So how a Newsstand app differ from other apps? first of all the icon presentation: a Newsstand compatible application will not see anymore its icon in the springboard, but a different and customizable icon will be shown in a special icon group called Newsstand. This icon will not be the typical square icon but instead it will be the cover of a magazine and furthermore this cover can be customized and keep aligned with the current latest issue available in the store.

Besides an app that decides to live behind the Newsstand umbrella will benefit of background downloading of new issues and the device can be informed, and proceed with automatic download eventually, of new issue by receiving special push notifications.

In order to achieve these, and several other features, an application must take in consideration several aspects, from icon management, to push notifications, inside the code and up to the iTunesConnect definition. In this tutorial we’ll try to build a simple Newsstand compatible app from scratch.

Application setup

Basic app

Prerequisite to follow this tutorial is to have XCode 4.2 and iOS 5 SDK installed. Even if most of the new features can be tested directly in the iPhone Simulator, the push notifcations and In App Purchase part of this tutorial require an iOS Developer Account.

So let’s start with a simple approach: open XCode and create a new “Empty” project, feel free to choose the target device between iPhone and iPad. Then create a new view controller, call it StoreViewController, and inside the app delegate instantiate a new object for this controller and put it inside a new navigation controller. Finally define a new icon, add it inside your project and run it in the simulator. The obvious result will be something like this, with our icon in the springboard and the app showing an empty screen:

Enabling for Newsstand

Now we need to instruct the system that we want our app to be compatible with Newsstand. The first requirement is to create an icon specific for Newsstand: this icon is different from the usual app icons at it must appear as a cover of our magazine. In our case we prepared a simple 130 x 190 px icon and imported in XCode. Besides we need to add or modify three new fields in the application Info.plist. The first new field is UINewsstandapp that must added and set to YES. The second is to create or modify the CFBundleIcons dictionary item by adding the Newsstand icons. Finally, if we want to support background download of new assets, we need to add the newsstand-code in the application background capabilities. The newly added plist items will appear in xml syntax as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
	<key>CFBundleIconFiles</key>
	<array>
		<string>icon.png</string>
		<string>icon@2x.png</string>
	</array>
	<key>CFBundleIcons</key>
	<dict>
		<key>CFBundlePrimaryIcon</key>
		<dict>
			<key>CFBundleIconFiles</key>
			<array>
				<string>icon.png</string>
				<string>icon@2x.png</string>
			</array>
			<key>UIPrerenderedIcon</key>
			<false/>
		</dict>
		<key>UINewsstandIcon</key>
		<dict>
			<key>CFBundleIconFiles</key>
			<array>
				<string>icon_newsstand.png</string>
			</array>
			<key>UINewsstandBindingEdge</key>
			<string>UINewsstandBindingEdgeLeft</string>
			<key>UINewsstandBindingType</key>
			<string>UINewsstandBindingTypeMagazine</string>
		</dict>
	</dict>
	<key>UINewsstandApp</key>
	<true/>
	<key>UIBackgroundModes</key>
	<array>
		<string>newsstand-content</string>
	</array>

Note that the CFBundleIconFiles section still exists for backward compatibility but in the new iOS 5 syntax it has been copied inside the CFBundlePrimaryIcon section of CFBundleIcons. These new settings appear in XCode as in the figure below, we have displayed here the values instead of the key names for the Info parameters, just to let you see how the new icon files structure is marked with an iOS 5 tag:

Let’s build and run the app again, the result will appear as below: our original icon has been removed from the springboard and replaced with the cover-like icon inside the Newsstand group; if we open the group then we’ll be able to click in our app icon to move it to foreground. Note that the classic style icons are still required by the system, as they are used in spotlight, for push notifications, in the App Store.

Displaying and downloading magazines

It’s time to now to start writing some code. The first operation is to link our project to the NewsstandKit framework. Don’t forget to import the NewsstandKit headers in the classed that need it:

1
#import <NewsstandKit/NewsstandKit.h>
For simplicity we suppose that the list of available issues (all free) is inside a plist file that can be downloaded from the publisher server. So for the purpose we define a class called Publisher which will take care of downloading the current list of items. As soon as the items have been downloaded they will be presented to the user. The plist is quite simple: it is an array of dictionaries, each dictionary represents an issue by providing the identifying name (unique), the title (visible to the user), the publishing date and the URLs for thumbnail cover image and content (PDF file) downloading.

1
2
3
4
5
6
7
8
9
10
11
12
	<dict>
		<key>Name</key>
		<string>Magazine-0</string>
		<key>Title</key>
		<string>Magazine Issue 0</string>
		<key>Date</key>
		<date>2011-10-01T06:00:00Z</date>
		<key>Cover</key>
		<string>http://www.viggiosoft.com/media/data/blog/newsstand/magazine-0.png</string>
		<key>Content</key>
		<string>http://www.viggiosoft.com/media/data/blog/newsstand/magazine-0.pdf</string>
	</dict>

At startup the StoreViewController will download the issues list from the server, parse it and will present this list in the table. Concurrently it will download the covers thumbnails and will show them in the table. This is quite typical code still not related to Newsstand so I will not explain the details. At the end of the loading the view will present itself in this way. You will simply the list of items together with their cover and a hint that invites to tap for download.

Now one of the most interesting things about the Newsstand framework is that the system manages for us the issues library by giving us access to a shared class called NKLibrary. Once we get the library, we’ll iterate through our issues list downloaded from the server and we will add our issues into the shared NK library. This can be done using the NKLibrary’s addIssueWithName:date: that needs as input only the unique name and publishing date for the issue and will return an NKIssue instance. Note that this data is only a subset of the data we need for our application (our simple issue dictionary contains in addition the display title, and the content and cover URLs), so we’ll need to maintain our own list and keep it in sync with the NKLibrary content. Also consider that once an issues has been added it will be permanently saved into the system NK library repository, even on subsequent runs of the application, this is because the operating system needs to maintain the status of all of our issues: this means that will be forbidden to add an issue with the same name twice, doing this will raise an exception and will crash the app. Going back to our code, concurrently with the downloading of the issues from the publisher’s server we’ll update the NKLibrary issues by adding only the ones not added yet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Publisher.m
// "issues" is the array of issues downloaded from the publisher's server
-(void)addIssuesInNewsstand {
    // shared NKLibrary instance
    NKLibrary *nkLib = [NKLibrary sharedLibrary];
    // we iterate through our issues and add only the one not in the NK library yet
    [issues enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSString *name = [(NSDictionary *)obj objectForKey:@"Name"];
        NKIssue *nkIssue = [nkLib issueWithName:name];
        if(!nkIssue) {
            nkIssue = [nkLib addIssueWithName:name date:[(NSDictionary *)obj objectForKey:@"Date"]];
        }
    }];
}

Note the way we manage the static information coming from the publisher with the dynamic information present in the NKLibrary. In particular we can link a given publisher’s issue with the corresponding NKIssue by the unique name identifier, thanks to the fact that NKLibrary exposes the method:

1
- (NKIssue *)issueWithName:(NSString *)name
Thanks to this link we are able to populate our table cells with a mix of static and dynamic information. This cell view infact other than providing the Title information (retrieved from the publisher’s list) will show an action label that will be “TAP TO DOWNLOAD” or “TAP TO READ” according to the fact that the magazine has been downloaded or not. Besides if the issue is under downloading we’ll show instead of the “TAP” message a bar that will show the download progress. We know the magazine status by checking the NKIssue status property, which has three possible values: NKIssueContentStatusAvailable, NKIssueContentStatusDownloading or NKIssueContentStatusNone.

Downloading an issue

Let’s explore now how issue download happens while the app is in foreground. This will be done by introducing a new class, called NKAssetDownload. First of all a few important concepts:

  1. each issue is composed of a single asset (as in this case: we just have a PDF file) or multiple assets (e.g.: pdf, images, videos, thumbnails); in the latter case the recommendation is to pack all files in a single zip and then download this single file.
  2. the final local destination path is established by the Newsstand framework and consist of a system assigned directory inside the app sandbox cache, so compliant with iCloud requirements
  3. this local destination can be retrieved from the NKIssue’s contentURL property
  4. we can build our final destination path by appending to the contentURL path our own file name, as we did in the piece of code below:
  5. 1
    2
    3
    
    -(NSString *)downloadPathForIssue:(NKIssue *)nkIssue {
        return [[nkIssue.contentURL path] stringByAppendingPathComponent:@"magazine.pdf"];
    }
    

So in our code when a table cell is tapped and the corresponding issue has not been downloaded yet we need to start the download. The code below retrieves the NKIssue object, then ask the Publisher instance for the original server URL where to download the issue (in a more complex app this request will require, for example, the In App Purchase receipt for validation before returning the download URL) and finally we’ll ask NKIssue for a new NKAssetDownload object that will be queued for downloading. Note that we store in the NKAssetDownload userInfo property the actual index of the table view cell. This is required as during our download we want to update the progress bar that can be retrieved only by querying for the right cell in the table. Even if this userInfo is a dictionary its storage is limited to plist-valid objects only, the penalty will be a runtime exception, so be careful!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    // let's retrieve the NKIssue
    NKLibrary *nkLib = [NKLibrary sharedLibrary];
    NKIssue *nkIssue = [nkLib issueWithName:[publisher nameOfIssueAtIndex:index]];
    // let's get the publisher's server URL (stored in the issues plist) 
    NSURL *downloadURL = [publisher contentURLForIssueWithName:nkIssue.name];
    if(!downloadURL) return;
    // let's create a request and the NKAssetDownload object
    NSURLRequest *req = [NSURLRequest requestWithURL:downloadURL];
    NKAssetDownload *assetDownload = [nkIssue addAssetWithRequest:req];
    [assetDownload setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:
                                [NSNumber numberWithInt:index],@"Index",
                                nil]];
     // let's start download
    [assetDownload downloadWithDelegate:self];

In the code above we set the assetDownlaod delegate property to self. This allows the view controller to monitor the download progress implementing the NKURLConnectionDownloadDelegate which is an extension (or a specialization) of the already known NSURLConnectionDelegate protocol. This protocol defines three methods:

1
2
3
 connection:didWriteData:totalBytesWritten:expectedTotalBytes:
 connectionDidResumeDownloading:totalBytesWritten:expectedTotalBytes:
 connectionDidFinishDownloading:destinationURL:

The first method allows us to monitor the progress status of our download. Thanks to the connection’s newsstandAssetDownload property we can retrieve the NKAssetDownload associated to the connection and then we’ll be able to retrieve the table view cell associated to this asset’s magazine thanks to the Index property we previously stored in the userInfo dictionary:

1
2
3
4
5
6
7
8
9
10
11
12
-(void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long)expectedTotalBytes {
    [self updateProgressOfConnection:connection withTotalBytesWritten:totalBytesWritten expectedTotalBytes:expectedTotalBytes];
}
-(void)updateProgressOfConnection:(NSURLConnection *)connection withTotalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long)expectedTotalBytes {
    // get asset
    NKAssetDownload *dnl = connection.newsstandAssetDownload;
    UITableViewCell *cell = [table_ cellForRowAtIndexPath:[NSIndexPath indexPathForRow:[[dnl.userInfo objectForKey:@"Index"] intValue] inSection:0]];
    UIProgressView *progressView = (UIProgressView *)[cell viewWithTag:102];
    progressView.alpha=1.0;
    [[cell viewWithTag:103] setAlpha:0.0];
    progressView.progress=1.f*totalBytesWritten/expectedTotalBytes;
}

While the implementation of the progress method is optional, all in all we are not strictly required to monitor the progress status, the next two methods must be implemented. One method informs us that the downloading of an asset has been resumed and that we can update our progress; we’ll see this detail later, for the moment we behave exactly as in the method above, by updating the progress bar. The other method instead needs to be implemented as it informs the delegate that the download has finished and that the downloaded content (currently stored in a temporary area) can be moved to its final location (that we know is given to us witth the contentURL property of NKIssue. Our implementation then moves the new content to its final location and refresh the table by removing the progress bar and replacing with the new “TAP TO READ” message:

1
2
3
4
5
6
7
8
9
10
11
-(void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL {
    // copy file to destination URL
    NKAssetDownload *dnl = connection.newsstandAssetDownload;
    NKIssue *nkIssue = dnl.issue;
    NSString *contentPath = [publisher downloadPathForIssue:nkIssue];
    NSError *moveError=nil;
    if([[NSFileManager defaultManager] moveItemAtPath:[destinationURL path] toPath:contentPath error:&moveError]==NO) {
        NSLog(@"Error copying file from %@ to %@",destinationURL,contentPath);
    }
    [table_ reloadData];
}

If your issue is made of multiple assets, of course you can queue them all. In such case other than monitoring the single assets downloads (at least to copy them in their final destination directory) you may be interested to know when all queued assets download have finished. In such case you can register to the NKIssueDownloadCompletedNotification that will be posted (without userInfo) by the interested issue.

An interesting point we would like to highlight is the way we’re downloading the magazine covers for our table. If you have a look at the code, you will see that as soon as the controller needs to provide the image for the table cell view, it will query the Publisher class sending the index of the cover to be retrieved and a completion handler block that will be executed by the publisher instance. The publisher instance infact does an async download of the cover data and when done runs the block that will update the table. This works perfectly but it can be done in a different way by using the Newsstand capabilities: infact if you consider that the image cover is, after all, an issue asset, you can still add this asset request to the system and in the connection delegate you can re-load the affected cell with the new item. We didn’t cover this aspect in this code, as we preferred to focus on the content download, but you may consider for your applications this approach which is more elegant.

Background downloading

One of the most interesting features of Newsstand is that once an asset downloading has started it will continue even if the application is suspended (that is: not running but still in memory) or it is terminated. Of course during while your app is suspended it will not receive any status update but it will be woken up in the background in order to process the download completion operation: in this case you will have the chance to copy the downloaded asset in its final destination or, for zipped content, to unpackage it.

In case that app has been terminated while downloading was in progress, the situation is different. Infact in the event of a finished downloading the app can not be simply woken up and the connection delegate finish download method called, as when an app is terminated its App delegate object doesn’t exist anymore. In such case the system will relaunch the app in the background and then the UIApplicationDelegate application:didFinishLaunchingWithOptions: will be called with the UIApplicationLaunchOptionsNewsstandDownloadsKey key in the launchOptions dictionary. If defined, this key will contain the array of all asset identifiers that caused the launch. From my tests it doesn’t seem this check is really required if you reconnect the pending downloading as explained in the next paragraph.

Another important thing to consider is that each time the app is relaunched, we need to check for existing pending downloads. If there any then we must reconnect them to the download delegate. If we don’t do this, then all not-reconnected downloads are considered abandoned by the system and they will be cancelled:

1
2
3
4
5
    // inside the app delegate's application:didFinishLaunchingWithOptions: and after the view controller has been initialized
    NKLibrary *nkLib = [NKLibrary sharedLibrary];
    for(NKAssetDownload *asset in [nkLib downloadingAssets]) {
        [asset downloadWithDelegate:store];
    }

New content notification

One of the most interesting features related to Newsstand it the possibility to receive push notifications when new content is available: this means that the app will be able in background to automatically download the new content and update the cover as appears inside the Newsstand group.

The implementation of this feature requires some preliminary setup. I will not enter in the detail of the operations required to have push notification working in your app, so I will limit myself to summarize the required steps:

  • In the developer portal, create a Bundle ID specific for your app, without wildcards
  • In the developer portal, enable the Bundle ID for push notifications and create the server certificate
  • Install the server certificate in your push notification servers. If you don’t have one, you can refer to external services such as Urban Airship (which is free for development and free up to some limit for production)
  • Check in your Info.plist that the Bundle ID in your code is the same that you used in the developer portal
  • In the developer portal create a provisioning profile for the app and use it when compiling and installing the app in your device

It is initially suggested to test if push notifications are working. So register the app for receiving basic push notifications and try from your push server to send a message. If you get it you’re ready to continue with Newsstand integration.

1
2
3
4
5
    // APNS standard registration to be added inside application:didFinishLaunchingWithOptions:
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |
                                                         UIRemoteNotificationTypeSound |
                                                         UIRemoteNotificationTypeAlert)];
}

Newsstand Kit adds a new remote notification type, called UIRemoteNotificationTypeNewsstandContentAvailability which informs the app that it is able to receive new content updates through push notifications. As our app is not interested in receiving other kind of push notifications, we can replace the code above with:

1
2
3
    // APNS standard registration to be added inside application:didFinishLaunchingWithOptions:
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeNewsstandContentAvailability];
}

When you start the app the first time you will get a new kind of alert. If you don’t get it then your setup is invalid and I suggest to implement and check the error message passed with the - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error method.

At this point you’re ready to send a push notification to your device informing Newsstand that a new issue is available. The push notification message has the same syntax as standard push notifications with the only difference that the payload must contain the content-available key set to 1:

1
2
3
4
5
6
{
  "aps":{
	"content-available":1,
        },
   "device_tokens": ["E9623F5CFDE92B40DA4AA90B97B70428BCD8FFCBA067BE17A6EF5102651E66E9"]
}

There is one importation limitation related to the frequency of pushes: to limit the consumption of resources due to background downloading (which, by the way, we’ll happen only if the device is connected to a Wi-Fi network) Newsstand push notifications are coalesced and only one background download is permitted per day. For testing purposes you can remove this limit by setting an appropriate key in the user defaults (do this at startup in your App Delegate):

1
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NKDontThrottleNewsstandContentNotifications"];

When a notification is sent your app can be in two possible states: running in the foreground or not. In the first case you must respond to the UIApplicationDelegate application:didReceiveRemoteNotification:, and your action could be simply alerting the user or starting the new download automatically or asking the server for the new issue data (title, cover, and so on). In the other case your app will be activated in the background again with a call to the application:didFinishLaunchingWithOptions: and what this method has to do, in addition to the other things is doing, is to check the launch options for the UIApplicationLaunchOptionsRemoteNotificationKey and retrieve the payload. In this case what can be done is to start a background task that will fetch the latest issue from the server of get the list of all issues and starting downloading the last one for example. Normally this background task should be completed in a couple of seconds, which is the time assigned by the operation system to start the automatic download. If this time is not considered enough, because you have to query your server before, then the suggested approach is to run this procedure inside a beginBackgroundTaskWithExpirationHandler: call thus getting about 10 minutes for the whole job.

The example below shows how the app can behave in case it is not running (neither in the foreground nor as inactive). In this simple example we assume the latest magazine is “Magazine-4” (but we could have given the name inside the notification payload) and then just schedule the download. At this point the app will terminate again but when the download terminates it is resumed, thanks to the internal rules of NK previously explained, and then the magazine is moved to its final location.

1
2
3
4
5
6
7
8
9
10
11
    NSDictionary *payload = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
    if(payload) {
        // schedule for issue downloading in background
        NKIssue *issue4 = [[NKLibrary sharedLibrary] issueWithName:@"Magazine-4"];
        if(issue4) {
            NSURL *downloadURL = [NSURL URLWithString:@"http://www.viggiosoft.com/media/data/blog/newsstand/magazine-4.pdf"];
            NSURLRequest *req = [NSURLRequest requestWithURL:downloadURL];
            NKAssetDownload *assetDownload = [issue4 addAssetWithRequest:req];
            [assetDownload downloadWithDelegate:store];
        }
    }

Updating the Newsstand icon

A new special feature introduced with Newsstand is the possibility to update the Newsstand app icon according to the last updated cover. The recommendation from Apple is to use the cover of the last updated issue. Another interesting possibility is that we can mark this icon with a New sash to mark that an issue is new and has not been read yet. Of course as soon as the app is restarted or the new issue has been read it is good practice to remove this marker. The way we can change the icon and set the sash visible is quite easy, just put this code somewhere after your download did finish and your issue has been safely saved in its destination path (never show the cover before it is available!):

1
2
3
4
5
6
    // update the Newsstand icon
    UIImage *img = [publisher coverImageForIssue:nkIssue];
    if(img) {
        [[UIApplication sharedApplication] setNewsstandIconImage:img];
        [[UIApplication sharedApplication] setApplicationIconBadgeNumber:1];
    }

Your screen will appear like this: the Newsstand icon badged, your app icon cover update to the new cover and the “New” sash on top of it.

Conclusions

In the next article we will talk about the interaction of a Newsstand app with the In App Purchase system and how to manage it using the iTunesConnect service. The full source code to be used more than a reference to better follow this tutorial than a real example to be run, can be found on GitHub.

At this point you may ask yourself if adding Newsstand for your next apps makes sense or not. There are several advantages but also drawbacks. The main drawback is that Newsstand works on devices that have migrated to iOS5. At the time of writing, after few days from iOS 5 distribution to the customers, the adoption rate is 1/3. We may expect by the end of 2011 an adoption rate greater than 80%. Anyway consider this point before starting delivering a magazine app with Newsstand and if you are doing this for a client then talk with her/him about this point. If you have a magazine already in the App Store and you’re considering to migrate it to Newsstand, then of course you will not lose your existing customer base as releasing an update will not impact all users that have installed the previous app versions, provided you don’t change your back-end, that is the server side web service remain compatible with old app versions. Clearly new users potential still stuck to iOS4 will not be able to install the newer iOS 5 versions.

The major advantages in using Newsstand are that from the point of view of user experience, it is improved as the user feels more natural to pick its magazine from a single stand; besides some features as latest cover and automatic download will be greatly appreciated. From the point of view of the publisher having an app in the Newsstand will bring a marketing advantage as Newsstand presents itself as a separate area in the store, quite visible and well promoted by Apple. Finally from the point of view of the developer you will be able to take advantage of many features, including a shared issues library and an efficient background download system, whose development from scratch can be difficult and requires care of many details: now some of the possible issues are managed by the framework and you, as developer, can spend more time on delivering a good user experience.

Comments