In-App Purchase – How to retrieve the list of products from the App Store

Share the joy
  •  
  •  
  •  
  •  

In the first part of this tutorial about In-App Purchase we have seen how to setup an app and configure everything in iTunes Connect. This second part assumes that you have already at least one product configured in iTunes Connect and its status is “Ready to Submit”. We’ll create an app that retrieves all the available IAP products from iTunes Connect and displays them in a table view. When you select a product you can see a details page with a description of the product. In order to do this we will use the Store Kit framework.  So let’s start a new Master-Detail Application project.

Remember that in the first part of this tutorial we have already chosen a name and a bundle for this app so make sure that this is the one you’re using for this new project:

PhotosProject

We’ll choose Swift as the language for this tutorial, but you can follow the same steps and have it working in Objective C too. The API for the framework is the same in Swift and Objective C, only the syntax will be different. After creating the project we want to go to the “Targets” section and see what capabilities are enabled for this app.

capabilities

All capabilities are off for our app, so we need to indicate that we want to use In-App Purchase. Turning this feature on will add the Store Kit framework to our project and add this capability to our app ID in the iOS developer centre.

IAP_On

Retrieving the list of products

We want to retrieve the product or products that we have configured in iTunes Connect and display them in a table view. In order to do this we’ll use the SKProductRequest class. To initialise an object of this class we need to give it a set of product identifiers. We could write all this code in MasterViewController.swift file, but I think it would be better to encapsulate it in a separate class. So let’s create a class called IAPHelper. This is where we are going to create the request, check the response and later add code to buy products.

Each product you configure to sell in iTunes Connect has a unique identifier and that identifier is what we need in order to be able to retrieve information about the product. You can choose to hard code the product identifiers in your code or to keep them in a plist. These two options are not very flexible and if you add a new product in iTunes Connect you need to re-submit your app for approval. The more flexible option is to request these identifiers from your own server. This way new products will be visible in the list of products retrieved from iTunes Connect without any code change. While this option is more flexible, it is an overkill when you don’t add new products often, so choose wisely. We’ll choose the simplest option for our example and hard code the product IDs in the init method of our IAPHelper file.

class IAPHelper: NSObject {    
    let productIdentifiers: NSSet    
    override init() {        
        productIdentifiers = NSSet(objects: "com.masteringios.Photos.WinterPhotos", "com.masteringios.Photos.CatPhotos", "com.masteringios.Photos.DogPhotos")
    }

Let’s create a method to retrieve products:

var completionHandler: ((Bool, [SKProduct]!) -> Void)!
    
func requestProductsWithCompletionHandler(completionHandler:(Bool, [SKProduct]!) -> Void){    
    self.completionHandler = completionHandler
        
    let productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)        
    productsRequest.delegate = self        
    productsRequest.start()
}

Let’s go through each line of code and understand what it does. Our method has one parameter, a completion handler that needs to be called when we have received a response. We store this handler to use it when the time is right.

Next we create a SKProductsRequest object using the product identifiers for the products setup in iTunes Connect. The request needs to have a delegate, so we’ll make this class the delegate as seen on the next row. You’ll need to indicate that the class conforms to the SKProductsRequestDelegate protocol in its definition. You’ll also need to import StoreKit.

Once we have initialised the request and set its delegate all that’s left to do is call the start method. This will send the request to the Apple App Store.
There are two delegate methods that we need to implement for our request: for when we receive a response and for the error case. The success one looks like this:
func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) {
        
    println("product count \(response.products.count)")
    println("invalid product IDs \(response.invalidProductIdentifiers)")
        
    if response.products.count > 0 {
        var products: [SKProduct] = []
            
        for prod in response.products {
           if prod.isKindOfClass(SKProduct) {
               products.append(prod as SKProduct)
            }
        }
            
        completionHandler(true, products)
    }
}
When we receive a response there are two arrays that we care about: the products array and the invalidProductIdentifiers one. Checking whether we have invalid product identifiers is very useful in the development stage, to see if everything is setup ok. We’re only just logging it to the console, not doing anything else with it. The more interesting array is the one containing the products. We want to pass the array as parameter for the completion handler, however, the types don’t match. The array in the response can contain object of type AnyObject (this is because the API has to be the same for Objective C and Swift and in Objective C you can’t specify that an array can contain only objects of type SKProduct). So we need to convert it to an array of SKProducts and that’s what the for loop is doing. Finally we can now call the completion handler.
Let’s write the method that handles the error case and then plug this code in the view controller to see if it’s working. The method is called request: didFailWithError: and all we do when we get this call is to call the completion handler with the appropriate parameters:
func request(request: SKRequest!, didFailWithError error: NSError!) {
        
    completionHandler(false, nil)
}

Updating the UI

We have written all the code needed to retrieve the products list from the App Store, however nobody calls the methods we have written. In this section we’re going to address that and we’re going to make sure that our products are displayed in the table view.

First let’s remove the “Edit” button and the “+” button that came with the master detail app template. You can find the code in the viewDidLoad method of MasterViewController. You can also remove the insertNewObject method.

In viewDidLoad we want to make the request for products. For this we need to declare and create an IAPHelper object. This will be at class level and we can make it a constant as we don’t change it at any point:

let helper = IAPHelper()

Then in viewDidLoad: call the method like this:

self.helper.requestProductsWithCompletionHandler({ (success, products) -> Void in
    //completion handler implementation goes here
})

We’re using a closure for the completion handler parameter and we need to provide the code for what should happen both for successful request and in case of an error. If we receive the list of products then we want to display it in the table view. The template already provided a mutable array of objects that acts like the data source for the table view, so we’re going to update it when we get back products. In case of an error we are going to show an alert view saying something went wrong. You should consider whether this is appropriate for a real life application, it might annoy the users if the app is used offline a lot. This is how the full request code looks like:

helper.requestProductsWithCompletionHandler({ (success, products) -> Void in
    if success {
        self.objects.addObjectsFromArray(products)
        self.tableView.reloadData()
    } else {
        let alert = UIAlertController(title: "Error", message: "Cannot retrieve products list right now.", preferredStyle: .Alert)
        alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
        self.presentViewController(alert, animated: true, completion: nil)
    }
})

There is one more thing we need to fix before we can run the app. The template was assuming that the objects are of type NSDate because that’s what the “+” button was adding to the array. However we know we have objects of type SKProduct. In tableView: cellForRowAtIndexPath: method make the following changes:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell

    let product = objects[indexPath.row] as SKProduct
    cell.textLabel!.text = product.localizedTitle
    return cell
}

Ok, let’s run our app and see the results!

productsSimulator

 

Debugging tips:  If you don’t see the list of products first check if you get back invalid IDs. If so, make sure that the spelling is right (the IDs are case sensitive, so double check that). Also check that the status for the products configured in iTunes Connect is “Ready to Submit”.

The app is now displaying the list of products, however if you select one the app will crash. Let’s fix this quickly so we can leave the app in a stable state for the next step.

The app is crashing because when we setup the detail view controller we are still assuming the objects array contains NSDate. We’ll fix this by going to the prepareForSegue: method and replacing NSDate with SKProduct:

let object = objects[indexPath.row] as SKProduct

We want the detail view controller to have as title the name of the product, so we also add this line of code:

controller.title = object.localizedTitle
In DetailViewController.swift file find the configureView method. We want to use the localizedDescription of the SKProduct and display it in the detail label:
func configureView() {
    if let detail: AnyObject = self.detailItem {
        if let label = self.detailDescriptionLabel {
            label.text = detail.localizedDescription
        }
    }
}
We need to make some small changes in the storyboard, make the text a bit bigger, change the position of the detail label and put 0 in the line counts so that it will display on multiple lines. You’ll break the auto layout rules so make sure you update the constraints after you have finished moving and resizing the label. And those are the only changes needed to have a working app. Your app should display a detail view that will look similar to this:
winterPhotosSimulator
In the next tutorial we are going to take things a step further and we’re going to implement code to allow the user to buy a product. You can find the code for this project here.
Did you find this tutorial useful? Please leave a comment, your feedback is greatly appreciated.

Share the joy
  •  
  •  
  •  
  •  

6 thoughts on “In-App Purchase – How to retrieve the list of products from the App Store

  1. Pingback: In-App Purchase – How to implement the payment transaction | Mastering iOS

  2. Hi, I keep getting this error: Type: ‘IAPPHelper’ does not conform to protocol ‘SKProductsRequestDelegate’ – What should I do?

    Best regards,
    Avi T.

    • You need to implement this method:

      func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!)

      Download the latest version of my project from Github and see how it is implemented. That is the only required method in that protocol, so once you have that you should not see the error anymore.

      • Hi,
        Once I’v successfully downloaded my products after purchase how do I present the downloaded content to the user?

        The Content contains a string of text that can be placed inside of a UILabel.

        Thanks in advanced,
        Your tutorials are amazing.
        AT.

  3. Hi there,

    Can you please let me know how I can fetch the list of In-app purchases of another app which is not mine?

    To give you a context:
    I’ve a page in my iOS app, which should show a list of In-app purchases of few apps. Out of these few apps, some are not owned by me.

    This is purely just to show the list of IAPs and their price which needs to be fetched dynamically every time the page loads.

    Kind regards,
    Peter.

    • Hi Peter,

      The only way I know of to retrieve a list of products is by supplying the product IDs. I don’t know of a way to retrieve the list of product IDs for an app. Sorry I can’t help.

Leave a Reply

Your email address will not be published. Required fields are marked *

*