In-App Purchases: Non-Renewing Subscriptions Tutorial

Learn to offer access to time-limited content to users and monetize your app using In-App Purchases in this Non-Renewing Subscriptions Tutorial. By Owen L Brown.

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

New Product ID Handling

Currently, new purchases coming from IAPHelper pass through OwlProducts.handlePurchase. But the starter project code only supported non-consumables. You’ll give it an overhaul. Replace the existing contents with:

if productIDsConsumables.contains(productID) {
  UserSettings.shared.increaseRandomRemaining(by: 5)
  setRandomProduct(with: true)
      
  NotificationCenter.default.post(name: NSNotification.Name(rawValue: PurchaseNotification),
                                  object: nil)
} else if productIDsNonRenewing.contains(productID), productID.contains("3months") {
  handleMonthlySubscription(months: 3)
} else if productIDsNonRenewing.contains(productID), productID.contains("6months") {
  handleMonthlySubscription(months: 6)
} else if productIDsNonConsumables.contains(productID) {
  UserDefaults.standard.set(true, forKey: productID)
  store.purchasedProducts.insert(productID)
      
  NotificationCenter.default.post(name: NSNotification.Name(rawValue: PurchaseNotification),
                                  object: nil)
}

Based on the purchased product type:

  1. Consumables: Increase random images remaining by 5 and
  2. Non-Renewing 3 months: increase subscription by 3 months
  3. Non-Renewing 6 months: increase subscription by 6 months
  4. Non-Consumables: update the IAPHelper store’s purchasedProducts

In all of the cases, the code posts the PurchaseNotification either directly from this function or indirectly. setRandomProduct is only called for cases dealing with the random owl image product, which includes all types except non-consumables.

Progress Check!

Wow, too much coding. Build and run to see your progress.

Cough up the cash!

Non-Renewing Subscriptions

Good news! The new subscriptions and consumable products are now showing. If you buy a subscription it still doesn’t let you view the random owl images though. You’ll fix that next.

That completes the subscription handling code. Now head over to the GUI side of things and display the new content to the user.

Providing Subscription Content

Up to this point, the new subscription and consumable products appear in the list but don’t display random owls when selected. You’ll fix that.
Open MasterViewController.swift and replace the tableView(_:didSelectRowAt:) code with the following:

let product = products[indexPath.row]
if OwlProducts.productIDsConsumables.contains(product.productIdentifier) 
  || OwlProducts.productIDsNonRenewing.contains(product.productIdentifier) {
    performSegue(withIdentifier: randomImageSegueIdentifier, sender: product)
} else {
  performSegue(withIdentifier: showDetailSegueIdentifier, sender: product)
}

Now the user can segue to the RandomViewController when selecting a product related to the random owl.

Next, you’ll update the functionality of the Restore feature. The Restore button allows users to retrieve purchases made on other devices or from a previous install of the app. Currently, the project only restores consumable products. Add the following to the bottom of restoreTapped(_:):

// Restore Non-Renewing Subscriptions Date saved in Parse
OwlProducts.syncExpiration(local: UserSettings.shared.expirationDate) {
  object in
      
  DispatchQueue.main.async {
    self.tableView.reloadData()
  }
}

This code calls syncExpiration to sync the expirationDate with the Parse Server. Once the app has the latest date, the completion handler reloads the product list with up-to-date status of each product.

Note: You’ll notice that restore doesn’t reload consumable products. This is by design. Consumables are not transferable between devices nor retainable when the app is deleted.

Build and run.

Randomly….. Empty!

Non-Renewing Subscriptions

Close, but no cigar. The random content is still missing from RandomViewController. Open RandomViewController.swift and add the following to refresh():

guard OwlProducts.paidUp() else {
  resetGUI()
  return
}

// User has paid for something
btnRandom.isHidden = false

// Get a random image, not same image as last time
var index = 0
let count = UInt32(OwlProducts.randomImages.count)

repeat {
  index = Int(arc4random() % count)
} while index == UserSettings.shared.lastRandomIndex

imageView.image = OwlProducts.randomImages[index]
UserSettings.shared.lastRandomIndex = index

Regarding the code above. The first few lines check to see if the user paid for the product. If not, no random images will display. Next, the code assumes all is good and enables btnRandom allowing the user to cycle through to the next random image. Finally, use arc4random to randomize the next image index. You’ll also notice the code prevents the same image from displaying twice in a row.

refresh() needs more functionality. You need something to display the expiration date to the user and to keep track of consumables used up by the user. Add the following to the bottom of refresh().

// Subscription or Times
if OwlProducts.daysRemainingOnSubscription() > 0 {
  lblExpiration.text = OwlProducts.getExpiryDateString()
} else {
  lblExpiration.text = "Owls Remaining: \(UserSettings.shared.randomRemaining)"
      
  // Decrease remaining Times
  UserSettings.shared.randomRemaining = UserSettings.shared.randomRemaining - 1
  if UserSettings.shared.randomRemaining <= 0 {
    OwlProducts.setRandomProduct(with: false)
  }
}

The code validates the user's expiration date. If paid up, it updates lblExpiration. If no valid subscription exists, it reduces the number of consumables remaining by 1. Each time the app calls refresh() another consumable consumed. Once all purchased images are gone, nothing more will display.

Note: Pressing btnRandom calls refresh(), which also uses up the purchased images.

Build and run to see the progress.
Purchase a subscription and check out some Owls.

Random Owls

Non-Renewing Subscriptions

Hoot! Hoot!

Clean Up

It would be nice if the subscription items in the table indicated if the subscription is already purchased. Plus, the Random Owl item needs to stand out a little to indicate it's our special random product.
Open ProductCell.swift and replace the line of code toggling the visibility of imgRandom with the following:

imgRandom.isHidden = (product.productIdentifier != OwlProducts.randomProductID)

This will show a special icon next to the Random Owl item in the table.

Next, insert the following code below at the TODO: marker.

else if OwlProducts.productIDsNonRenewing.contains(product.productIdentifier) {
  btnBuy.isHidden = false
  imgCheckmark.isHidden = true
        
  if OwlProducts.daysRemainingOnSubscription() > 0 {
    btnBuy.setTitle("Renew", for: .normal)
    btnBuy.setImage(UIImage(named: "IconRenew"), for: .normal)
  } else {
    btnBuy.setTitle("Buy", for: .normal)
    btnBuy.setImage(UIImage(named: "IconBuy"), for: .normal)
  }
        
  ProductCell.priceFormatter.locale = product.priceLocale
  lblPrice.text = ProductCell.priceFormatter.string(from: product.price)
}

This code sets the Buy button to Renew for valid subscriptions. You'll still want to let users buy additional months, but it should always be clear to a user what they've purchased. Finally, the last couple lines of code display the pricing for the subscription.

Build and run.

Hoot! Hoot!

Non-Renewing Subscriptions

The product list should now behave correctly. If you purchase everything, the Subscribe buttons should all change to Renew and the Buy buttons change to checkmarks:

Where to Go from Here?

Here's the completed sample project.

Congratulations! You've implemented in-app non-renewing subscriptions in your app.

For many simple apps this approach is more than sufficient. But if you want to take things even further check out the other IAP tutorials and videos on the site. Also, checkout the WWDC 2016: Introducing Expanded Subscriptions in iTunes Connect video for all the cool new features.

I hope you enjoyed this tutorial. Please join the forum below for any questions or comments!

Owen L Brown

Contributors

Owen L Brown

Author

Darren Ferguson

Tech Editor

Essan Parto

Final Pass Editor

Andy Obusek

Team Lead

Over 300 content creators. Join our team.