34,333
edits
Changes
no edit summary
{{#pagetitle: An iOS 11 In-App Purchase Tutorial }}
<seo title="iOS 11 App Development Essentials" titlemode="replace" keywords="ios 11, swift 4, ebook, in app purchase, xcode 9" description="A detailed tutorial that teaches how to integrate in-app purchasing into an iOS app."></seo>
This chapter assumes that the steps outlined in the previous chapter ([[Preparing an iOS 8 Application for In-App Purchases|Preparing an iOS 11 Application for In-App Purchases]]) have been followed and implemented carefully. This chapter will continue the development of the InAppDemo project to demonstrate the application side of in-app purchasing.
== The Application User Interface ==
When completed, the application will consist of three views (Figure 114-1). The goal is for the Level 2 view to be inaccessible from the Level 1 view until the user has made an in-app purchase using the buy button:
[[File:]]
Figure 114-1
== Designing the Storyboard ==
Load the InAppDemo project into Xcode, edit the Main.storyboard file, select the View Controller scene and choose the Editor -> Embed In -> Navigation Controller menu option to add a navigation controller to the storyboard.
Next, design the user interface for the Level 1 screen as illustrated in Figure 114-2 below:
[[File:]]
Figure 114-2
Select all of the views in the scene and use the Auto Layout Align menu to align all of the views horizontally within the containing view. With all of the views still selected, display the Add New Constraints menu and add a spacing to nearest constraint setting to the top edge of each view with the Constrain to margins option disabled.
Using the Assistant Editor with the ViewController.swift file selected, establish an Outlet connection for the “Enter Level 2” button named level2Button. Repeat this step to establish outlet connections for each of the remaining views to variables named as follows:
* productTitle
*productDescription
* productPrice
* buyButton
With the Assistant Editor still displayed, establish an action connection from the Buy button to a method named buyProduct. Complete the layout design for this scene by removing the default “Label” text from each of the label views.
Add another scene to the storyboard by dragging and dropping a View Controller object from the Object Library onto the canvas. Add a label to the view of the new scene and change the label’s text to ”Welcome to Level 2”. With the label selected, use the Auto Layout Align menu to enable horizontal and vertical container constraints for the view.
Establish a segue from the “Enter Level 2” button to the Level 2 scene by Ctrl-clicking and dragging from the button to the new scene. Release the line and select show from the resulting menu.
Select the “Enter Level 2” button, display the Attributes Inspector and turn off the Enabled checkbox in the Control section. This will ensure that the button is disabled until the user has purchased access to level 2.
When these steps are complete, the storyboard should appear as shown in Figure 114-3.
[[File:]]
Figure 114-3
== Configuring the View Controller Class ==
In this example, the View Controller class will serve as both the transaction observer and product request delegates. Edit the ViewController.swift file to make these declarations and to add additional properties that will be needed later in the chapter (where <YOUR PRODUCT ID GOES HERE> is replaced by the identifier for the In App Purchase product created using the iTunes Connect portal in the previous chapter):
<pre>
import UIKit
import StoreKit
class ViewController: UIViewController, SKPaymentTransactionObserver, SKProductsRequestDelegate {
@IBOutlet weak var level2Button: UIButton!
@IBOutlet weak var productTitle: UILabel!
@IBOutlet weak var productDescription: UILabel!
@IBOutlet weak var productPrice: UILabel!
@IBOutlet weak var buyButton: UIButton!
var product: SKProduct?
var productID = "<YOUR PRODUCT ID GOES HERE>"
.
.
.
</pre>
Note that purchase attempts will fail if the product ID specified does not match that defined for the in-app purchase item created using iTunes Connect.
== Initiating and Handling the Purchase ==
The first steps in completing the ViewController class are to add some code to the viewDidLoad method. To begin with, until product information has been obtained and displayed to the user, the buy button should be disabled. The class also needs to be configured as the transaction observer for the purchase operation. Finally, a method needs to be called to obtain the product information for the purchase and display it to the user. To achieve these tasks, edit the PurchaseViewController.swift file and modify the viewDidLoad method accordingly:
<pre>
override func viewDidLoad() {
super.viewDidLoad()
SKPaymentQueue.default().add(self)
getProductInfo()
}
</pre>
It will be the job of the getProductInfo method called from the viewDidLoad method above to contact the App Store and get product information for the specified ID and display it. The code for this method belongs in the ViewController.swift file and reads as follows:
<pre>
func getProductInfo()
{
if SKPaymentQueue.canMakePayments() {
let request = SKProductsRequest(productIdentifiers:
NSSet(objects: self.productID) as! Set<String>)
request.delegate = self
request.start()
} else {
productDescription.text =
"Please enable In App Purchase in Settings"
}
}
</pre>
The request for product information will result in a call to the didReceiveResponse delegate method which should be implemented as follows:
<pre>
func productsRequest(_ request: SKProductsRequest, didReceive response:
SKProductsResponse) {
var products = response.products
if (products.count != 0) {
product = products[0]
if let product = product {
buyButton.isEnabled = true
productTitle.text = product.localizedTitle
productDescription.text = product.localizedDescription
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = product.priceLocale
productPrice.text = formatter.string(from: product.price)
}
} else {
productTitle.text = "Product not found"
}
let invalids = response.invalidProductIdentifiers
for product in invalids
{
print("Product not found: \(product)")
}
}
</pre>
Note that the above code displays the product information to the user. This includes code to identify the locale for the price which is then formatted using the Swift NumberFormatter class configured to handle currency values. This ensures that the price is automatically displayed in the correct format and currency for the user’s locale. The method also enables the Buy button which was configured to call the buyProduct method, the stub for which now needs to be completed:
<pre>
@IBAction func buyProduct(_ sender: Any) {
if let product = product {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
}
</pre>
This code will initiate the purchasing process and cause calls to be made to the updatedTransactions method of the transaction observer object. Since the PurchaseViewController instance was declared as the transaction observer, this method also needs to be implemented in PurchaseViewController.swift:
<pre>
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions
transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case SKPaymentTransactionState.purchased:
self.unlockFeature()
SKPaymentQueue.default().finishTransaction(transaction)
case SKPaymentTransactionState.failed:
SKPaymentQueue.default().finishTransaction(transaction)
default:
break
}
}
}
</pre>
Regardless of the success or otherwise of the purchase, the code finishes the transaction. In the event of a successful purchase, however, the unlockFeature method will be called, and should now be implemented in ViewController.swift as follows:
<pre>
func unlockFeature() {
level2Button.isEnabled = true
buyButton.isEnabled = false
productTitle.text = "Item has been purchased"
}
</pre>
This method obtains a reference to the home view controller stored in the application delegate and calls the enableLevel2 method of that view controller instance. The Buy button is then disabled and the text displayed on the product title label changed to indicate the successful purchase.
== Testing the Application ==
Connect an iOS device to the development system (in-app purchasing cannot be tested in the iOS Simulator environment). In the Settings application on the device, choose the iTunes & App Store option, select your usual account and choose Sign Out from the popup dialog.
Run the application and note that the “Enter Level 2” button is initially disabled. Touch the “Purchase Level 2 Access” button and, after a short delay, note that the product information appears on the purchase screen. Select the Buy button, login using the sandbox account created in the previous chapter and wait for the purchase confirmation dialog to appear (Figure 114 4). Note that the dialog includes text which reads “[Environment: Sandbox]” to indicate that the sandbox is being used and that this is not a real purchase.
[[File:]]
Figure 114-4
When the purchase is complete Level 2 should now be accessible from the first scene.
== Troubleshooting ==
By just about any standard, in-app purchasing is a multistep process and, as with any multistep process, implementation of in-app purchases can be susceptible to errors.
In the event that the example application does not work there are few areas that are worthwhile checking:
* Verify that the application bundle ID matches the one used to create the provisioning profile and the app entry in iTunes Connect.
* Make sure that the matching developer profile is being used to sign the application.
* Check that the product ID used in code matches the ID assigned to the in-app purchase item in iTunes Connect.
* Verify that the item was configured as being available for purchase in iTunes Connect.
* Make sure that Tax and Banking details are entered and correct in iTunes Connect.
* Try deleting the application from the device and re-installing it.
== Promoting the In-App Purchase ==
The final task in this chapter is to experiment with promoting the in-app purchase item. The first step is to upload an image within the iTunes Connect portal to represent the item in the App Store. Log into iTunes Connect and select the My Apps option followed by the entry for the InAppDemo app. Within the settings screen for the app, select the Features tab followed by the in-app purchase item used in this example. Once the details for the in-app purchase have appeared, scroll down to the App Store Promotion (Optional) section. Click on the button to upload an image and select the in_app_promotion.jpg image located in the inappdemo_image folder contained within the sample code download for the book:
[[File:]]
Figure 114-5
Click on the Save button followed by the App Store Promotions option located in the left-hand navigation panel (highlighted in Figure 114-6).
[[File:]]
Figure 114-6
Enable the check box next to the in-app purchase item and click the Save button.
When the user selects the promoted item in the App Store, the app will be launched so that the purchase can be completed. This requires the addition of one more delegate method within the ViewController.swift file as follows:
<pre>
func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment
payment: SKPayment, for product: SKProduct) -> Bool {
return true
}
</pre>
Since the example app created in this chapter is not published in the App Store, a special link can be constructed to test that the purchase can be completed. The format of this link is as follows:
itms-services://?action=purchaseIntent&bundleId= <bundleID>&productIdentifier= <productID>
When creating the link for your app, the <BundleID> field is replaced by the bundle identifier for your app (for example com.example.InAppDemo) and the <productID> field by the identifier of the in-app purchase item (for example com.example.level2access).
Once the link has been constructed, send it to the device in a text message or email and click on it. The InAppDemo app should launch and automatically initiate the in-app purchase process.
== Summary ==
This chapter has taken the steps to complete a demonstration of in-app purchasing from within an iOS 11 application and provided some guidance in terms of troubleshooting tips in the event that in-app purchasing does not work. The example also provided an example of in-app promotion implementation and testing.