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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
In-App Purchases: Non-Renewing Subscriptions Tutorial
30 mins
- When to Use Non-Renewing Subscriptions
- Implementing Non-Renewing Subscriptions: Overview
- Getting Started
- To Do List
- Parse Server & Heroku
- iTunes Connect
- Add In-App Purchase Items
- Sandbox Surprises
- Adding Your Subscriptions to the Product List
- Expiration Handling
- Parse Server Sync
- New Product ID Handling
- Progress Check!
- Providing Subscription Content
- Clean Up
- Where to Go from Here?
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:
- Consumables: Increase random images remaining by 5 and
- Non-Renewing 3 months: increase subscription by 3 months
- Non-Renewing 6 months: increase subscription by 6 months
- 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.
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.
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.
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.
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.
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.
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!