Ad

Our DNA is written in Swift
Jump

Implementing an In-App App Store

If you have an app like prod.ly, which shows a timeline of user’s messages you probably want interactive hyperlinks. In two previous posts I showed how I am customizing UILabel to customize hyperlinks drawing, as well as how to make them react to touches. For most links a UIWebView will do to show the content.

But not for content that people can purchase and/or download from iTunes or the Apple App Store. For this, there is SKStoreProductViewController. This post shows how to implement it.

Apple introduced this view controller in iOS 6. Indeed it might be one of the most overlooked additions to StoreKit, as I don’t remember see it every in use in an iOS app. The following figure shows how the prod.ly app looks like in this view controller. Almost like in the App Store app, which is why I like to call it my “In-App App Store”.

In-App App Store

Apple had switched to using PHG as provider of their affiliate platform in late 2013. At this time there was no way to earn money with presenting by presenting other people’s products. So the primary use case probably was for developers to promote their other apps and let users download those right from within their app.

Two years later, in iOS 8, Apple finally added the ability to add an affiliate ID and campaign identifier. I can imagine that many enhancement requests must have been filed to that effect. Especially for apps with a lot of usage making a cut on selling other people’s content can be worthwhile.

Regardless of with our without taking part in the affiliate program, the following details how to implement the view controller.

Parsing the ID out of iTunes URLs

For presenting the store view controller you need the iTunes ID. This is the long number contained in all app store or iTunes URLs. To get it out of an NSURL I use this code:

NSNumber *PRYiTunesIDFromURL(NSURL *URL) {
   if (![[[URL host] lowercaseString] containsString:@"apple.com"])
   {
      return nil;
   }
 
   NSString *string = [URL path];
 
   NSRegularExpression *regex = 
       [NSRegularExpression regularExpressionWithPattern:@"id([0-9_]+)" 
                                                 options:0 
                                                   error:NULL];
   NSRange entireString = NSMakeRange(0, [string length]);
   NSArray *matches = [regex matchesInString:string 
                                     options:0 
                                       range:entireString];
 
   if ([matches count])
   {
      NSTextCheckingResult *result = matches[0];
      NSRange range = [result rangeAtIndex:1];
 
      NSString *identifier = [string substringWithRange:range];
      long long l = [identifier longLongValue];
 
      if (l)
      {
         return @(l);
      }
   }
 
   return nil;
}

This function returns an NSNumber with the identifier we need. I opted to have it as a number object instead of a scalar value because this is how we’ll need it in the next section.

Presenting the Store

For getting the store view controller to show, you need to configure it and present it. Let’s first look at the code before I explain the reason why it looks like that, there are a few hurdles (read “bugs”) to circumnavigate.

NSNumber *iTunesID = PRYiTunesIDFromURL(URL);
 
if (iTunesID)
{
   SKStoreProductViewController *storeProductViewController = [[SKStoreProductViewController alloc] 
                                                               init];
 
   // Configure View Controller
   [storeProductViewController setDelegate:self];
   [storeProductViewController loadProductWithParameters:
                                  @{SKStoreProductParameterITunesItemIdentifier : iTunesID}
    completionBlock:^(BOOL result, NSError *error) {
       // log error
    }
   ];
 
   // present right away to avoid pause
   [self presentViewController:storeProductViewController animated:YES completion:nil];
}

For course you need the #import for the StoreKit.h header. And the current view controller where you put this code needs to be tagged as supporting the SKStoreProductViewControllerDelegate protocol. But since both of these need angle brackets – which are a pain in the backside to write on my blog – I am telling you about those instead of showing them.

In terms of API design, Apple’s intention is for you to first load the product into the view controller asynchronously. Then when it’s loaded you can could show the filled view. There are two problems with this approach:

  1. It might take a couple of seconds for the product to be loaded. So you would have to implement and show a modal activity indicator before the view controller can be presented. This would bring the UI flow to a screeching halt from the point of view of the user.
  2. There is a bug in SKStoreProductViewController that it does not call the completion block if the product for this iTunes ID does not exist. You have no way of knowing that and your UI would be stuck forever. (rdar://20089825)

The only course of action available to us is to present the view controller right after the load method. It does deal with problems gracefully, like having no internet connection. Also if it takes longer than a fraction of a seconds to load the product there is a small activity indicator on the view controller that tells the user that something is still alive there.

When I first experimented with the in-app store, I wanted to present it on a navigation controller. You can do this, but then you are missing out on some extra buttons iOS puts in the navigation bar for you. Besides the cancel button there is also an action button for people to gift this item or send a link to it to somebody. There is also a Store button that takes you to the item’s page on Apple’s store.

In short, just present it as shown and don’t worry about needing a navigation controller wrapper as you would usually require for presenting view controllers modally. SKStoreProductViewController takes care of all it needs to present the top nav bar and its contents.

Closing the Store

The store view controller displays a Cancel button in the top left corner of its nav bar. There is another bug hidden there (rdar://20090284). If you fail to implement the delegate method, then Canceling has the effect of destroying your view hierarchy. The user ends up with a blank view and no recourse.

I suggested to Apple to either make the delegate method mandatory or fix the default cancel behavior. I’d prefer the latter because in my opinion a view controller which shows a Cancel button during modal presentation should also dismiss itself properly.

So, as it stands now in iOS 8.2, you need this code for the delegate:

- (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController
{
   [viewController dismissViewControllerAnimated:YES completion:NULL];
}

The same code – including the ID extraction – works for links to albums or music on iTunes, as well as apps on the app store. In the following figure I supplied the ID for an album of a famous Austrian artist, presented from within the same app:

In-App iTunes Store

Isn’t that smart?

Conclusion

Presenting an iTunes or App Store item from within your app is very simple to do. Fortunately the existing bugs can be circumvented easily. Because there is now an ability to specify affiliate information this store view controller should see much wider use now that people can earn some affiliate commissions with it.


Categories: Recipes

Leave a Comment