App crashes with in-app purchase error - ios

I use this code to create my in-app purchase:
import UIKit
import StoreKit
class ViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver, SKStoreProductViewControllerDelegate {
var product_id: String?
#IBOutlet weak var buyButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
product_id = "product_id"
SKPaymentQueue.default().add(self)
}
#IBAction func unlockAction(sender: AnyObject) {
print("About to fetch the product...")
// Can make payments
if (SKPaymentQueue.canMakePayments())
{
let productID:NSSet = NSSet(object: self.product_id!);
let productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>);
productsRequest.delegate = self;
productsRequest.start();
print("Fetching Products");
}else{
print("Can't make purchases");
}
}
func buyProduct(product: SKProduct){
print("Sending the Payment Request to Apple");
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment);
}
func productsRequest (_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let count : Int = response.products.count
if (count>0) {
let validProduct: SKProduct = response.products[0] as SKProduct
if (validProduct.productIdentifier == self.product_id) {
buyProduct(product: validProduct);
} else {
print(validProduct.productIdentifier)
}
} else {
print("nothing")
}
}
But if I click on button(unlockAction) twice or click once, move on different controller, return and click once more my app crashes on this line:
SKPaymentQueue.default().add(payment);
with error (lldb)
How to fix it?

You have to use viewWillDissappear() function for that in your billing controller. Try that one .
override func viewWillDisappear(_ animated: Bool) {
SKPaymentQueue.default().remove(self)
}
I was also getting that problem and that has been resolve my problem.
The error was with SKPaymentQueue.default().add(payment) line in the code. So try this solution and be reliable productive programmer.
Happy Coding

Related

SKProductsRequest returning 0 Products

I'm trying to do IAP however for some reason, my SKProductsRequest returns 0 products.
-Test products have been added to iTunes connect properly
-Banking and Taxing information is filled
-The product's bundle id matches the app bundle id
-I've waited up to two days for it to get processed through the servers
I used this youtube tutorial to build the app:
https://www.youtube.com/watch?v=zRrs7O5yjKI
And here is the code:
import UIKit
import StoreKit
class ViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
#IBOutlet weak var lblAd: UILabel!
#IBOutlet weak var lblCoinAmount: UILabel!
#IBOutlet weak var outRemoveAds: UIButton!
#IBOutlet weak var outAddCoins: UIButton!
#IBOutlet weak var outRestorePurchases: UIButton!
var coins = 50
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
outRemoveAds.isEnabled = false
outAddCoins.isEnabled = false
outRestorePurchases.isEnabled = false
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID: NSSet = NSSet(objects: "com.IAPTesters.10Dolla", "com.IAPTesters.RemoveAds")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
}
#IBAction func btnRemoveAds(_ sender: Any) {
print("rem ads")
for product in list {
let prodID = product.productIdentifier
if(prodID == "com.IAPTesters.RemoveAds") {
p = product
buyProduct()
}
}
}
#IBAction func btnAddCoins(_ sender: Any) {
for product in list {
let prodID = product.productIdentifier
if(prodID == "com.IAPTesters.10Dolla") {
p = product
buyProduct()
}
}
}
#IBAction func btnRestorePurchases(_ sender: Any) {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
func buyProduct() {
print("buy " + p.productIdentifier)
let pay = SKPayment(product: p)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(pay as SKPayment)
}
func removeAds() {
lblAd.removeFromSuperview()
}
func addCoins() {
coins += 50
lblCoinAmount.text = "\(coins)"
}
var list = [SKProduct]()
var p = SKProduct()
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("product request")
let myProduct = response.products
for product in myProduct {
print("product added")
print(product.productIdentifier)
print(product.localizedTitle)
print(product.localizedDescription)
print(product.price)
list.append(product)
}
outRemoveAds.isEnabled = true
outAddCoins.isEnabled = true
outRestorePurchases.isEnabled = true
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
print("transactions restored")
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "com.IAPTesters.RemoveAds":
print("remove ads")
removeAds()
case "com.IAPTesters.10Dolla":
print("add coins to account")
addCoins()
default:
print("IAP not found")
}
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("add payment")
for transaction: AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
print(trans.error)
switch trans.transactionState {
case .purchased:
print("buy ok, unlock IAP HERE")
print(p.productIdentifier)
let prodID = p.productIdentifier
switch prodID {
case "com.IAPTesters.RemoveAds":
print("remove ads")
removeAds()
case "com.IAPTesters.10Dolla":
print("add coins to account")
addCoins()
default:
print("IAP not found")
}
queue.finishTransaction(trans)
case .failed:
print("buy error")
queue.finishTransaction(trans)
break
default:
print("Default")
break
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
When you run the app it will print "IAP is enabled, loading" and than "product request" but nothing else.
If I print response.invalidProductIdentifiers in the productsRequest function it will return my products: ["com.IAPTesters.RemoveAds", "com.IAPTesters.10Dolla"]
Thanks in advance for the help
It turns out that my banking and taxing information was filled out incorrectly. I refilled it and than I had to wait about 30 minutes for it to work again. Everything is now working correctly! Thanks for all the help
If you refer to the SKProductsRequest documentation you will see:
Note
Be sure to keep a strong reference to the request object; otherwise, the system might deallocate the request before it can complete.
Your SKProductsRequest instance is a local constant in viewDidLoad. This will be deallocated as soon as viewDidLoad exits and since the product request will complete asynchronously, this will be before the request is completed and you will therefore never get a call back.
You should retain your SKProductsRequest in a property so that it isn't released.
class ViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
#IBOutlet weak var lblAd: UILabel!
#IBOutlet weak var lblCoinAmount: UILabel!
#IBOutlet weak var outRemoveAds: UIButton!
#IBOutlet weak var outAddCoins: UIButton!
#IBOutlet weak var outRestorePurchases: UIButton!
var productsRequest: SKProductsRequest?
var coins = 50
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
outRemoveAds.isEnabled = false
outAddCoins.isEnabled = false
outRestorePurchases.isEnabled = false
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID: NSSet = NSSet(objects: "com.IAPTesters.10Dolla", "com.IAPTesters.RemoveAds")
self.productsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request?.delegate = self
request?.start()
} else {
print("please enable IAPS")
}
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("product request")
let myProduct = response.products
for product in myProduct {
print("product added")
print(product.productIdentifier)
print(product.localizedTitle)
print(product.localizedDescription)
print(product.price)
list.append(product)
}
outRemoveAds.isEnabled = true
outAddCoins.isEnabled = true
outRestorePurchases.isEnabled = true
self.productsRequest = nil
}
FYI, your implementation of paymentQueueRestoreCompletedTransactionsFinished is incorrect; you should process the restored transactions in updatedTransactions through the .restored transaction state. The paymentQueueRestoreCompletedTransactionsFinished method should only be used to update your UI or perform any other tasks that are required when the restoration process is complete.
If I 'm understood correctly then the problem with your implementation is you have created your products in iTunes with wrong identifiers. As I know the product identifiers should be like your application bundle identifier followed by your product functionality i.e, COM.COMPANYNAME.MYNEWAPPLICATION.REMOVEADS, COM.COMPANYNAME.MYNEWAPPLICATION.10DOLLA
Sample code be like,
typealias SSIAPHelperCompletion = (_ result: [SKProduct]?, _ error: Error?) ->Void
class SSIAPHelper: NSObject {
fileprivate let productsRequest = SKProductsRequest(productIdentifiers: Set(arrayLiteral: "com.companyname.appname.removeads","com.companyname.appname.10dolla"))
fileprivate var completion: SSIAPHelperCompletion?
static let shared = SSIAPHelper()
func requestProducts(completionHandler: #escaping SSIAPHelperCompletion) {
self.completion = completionHandler
self.productsRequest.delegate = SSIAPHelper.shared
self.productsRequest.start()
}
}
extension SSIAPHelper: SKProductsRequestDelegate{
func requestDidFinish(_ request: SKRequest) {
print(#function)
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print(#function, response.products)
if let lCompletion = self.completion{
lCompletion(response.products, nil)
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print(#function)
}
}
You can get products by simply calling below function from your viewDidLoad()
SSIAPHelper.shared.requestProducts(completionHandler: { (result, error) in
})

Unable to purchase IAP as sandbox user

I am having a problem trying to purchase an IAP as a sandbox user. It's not the sandbox account that's the problem. It's the fact that my var products = [SKProduct]() array in IAPService.swift is empty.
Inside my StoreViewController.swift (which also stores my Game Center leaderboards):
class StoreViewController: UIViewController, GKGameCenterControllerDelegate {
#IBAction func purchase(_ sender: UIButton) {
IAPService.shared.purchase(product: .nonConsumable)
}
...
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
IAPService.shared.getProducts()
print(IAPService.shared.products) // This is an empty array?
authenticateLocalPlayer() // Related to Game Center
}
}
Inside my IAPProducts.swift
enum IAPProducts: String {
case nonConsumable = "com.nameofgame.nameofproduct"
}
Inside my IAPService.swift
import Foundation
import StoreKit
class IAPService: NSObject {
private override init() {}
static let shared = IAPService()
var products = [SKProduct]()
let paymentQueue = SKPaymentQueue.default()
func getProducts() {
let products: Set = [IAPProducts.nonConsumable.rawValue,
]
let request = SKProductsRequest(productIdentifiers: products)
request.delegate = self
request.start()
paymentQueue.add(self)
}
func purchase(product: IAPProducts) {
guard let productToPurchase = products.filter({ $0.productIdentifier == product.rawValue }).first else { return }
let payment = SKPayment(product: productToPurchase)
paymentQueue.add(payment)
}
}
extension IAPService: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
products = response.products
}
}
extension IAPService: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
if transaction.payment.productIdentifier == IAPProducts.nonConsumable.rawValue {
print("IAP Purchased")
}
break
case .failed:
SKPaymentQueue.default().finishTransaction(transaction)
print(transaction.error!)
break
default: break
}
}
}
}
Things I've done several things to try and solve the problem:
Changed the Set in IAPService to an NSSet. This doesn't work because the set of Strings in let request = SKProductsRequest(productIdentifiers: products) cannot be converted to an NSSet.
Checked multiple times that my BundleIDs match on Xcode and iTunes Connect. I've also done this check with the Product IDs to make sure there's a match between the IAP ID in iTunes Connect and the IAPProducts enum.
I added this IAP on iTunes Connect 3 days ago, and I've checked that all my contracts for paid apps are in effect.
Please help me with this issue. Thanks.
From the SKProductsRequest documentation
Note
Be sure to keep a strong reference to the request object; otherwise, the system might deallocate the request before it can complete.
Since you are creating your SKProductsRequest instance as a local variable, it is being released as soon as the getProducts function returns, before the delegate method can be called.
You need to use a property to hold a strong reference to your products request:
class IAPService: NSObject {
private override init() {}
static let shared = IAPService()
var products = [SKProduct]()
var request: SKProductsRequest?
let paymentQueue = SKPaymentQueue.default()
func getProducts() {
let products: Set = [IAPProducts.nonConsumable.rawValue,
]
self.request = SKProductsRequest(productIdentifiers: products)
request?.delegate = self
request?.start()
paymentQueue.add(self)
}
func purchase(product: IAPProducts) {
guard let productToPurchase = products.filter({ $0.productIdentifier == product.rawValue }).first else { return }
let payment = SKPayment(product: productToPurchase)
paymentQueue.add(payment)
}
}
extension IAPService: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if !response.invalidProductIdentifiers.isEmpty {
print("Invalid products identifiers received")
}
products = response.products
self.request = nil
}
func request(_ request: SKRequest, didFailWithError error:Error) {
print("Product request failed: \(error.localizedDescription)")
}
}

Swift/Xcode 9 - In App Purchases - unexpectedly found nil while unwrapping an Optional value

I am having some difficulties with removing ads from my app.
The issue is when I click the remove ads button, the application crashes with the message fatal error: unexpectedly found nil while unwrapping an Optional value.
I have looked online, however, they seem to be outdated.
I have looked at my console and I may have found the error product = (SKProduct)nil
The funny thing is I had implemented this method once before and had the same issue, and out the blue it worked. So I just re-used the code and change the productID.
I thought maybe it needed time to actually register the id of the in-app purchase. However, I've woken up to the same issue. Could someone please help me and tell me what the issue may be?
My game crashes once I tap the Remove Ads button. The action/method for my remove button is removeAds_TouchUpInside.
import UIKit
import SpriteKit
import GameplayKit
import GoogleMobileAds
import StoreKit
class GameViewController: UIViewController, GADInterstitialDelegate, SKPaymentTransactionObserver, SKProductsRequestDelegate {
var fullScreenAds : GADInterstitial!
#IBOutlet weak var removeAdsButton: UIButton!
var product: SKProduct?
var productID = "com.USER.GAME.removeAds"
override func viewDidLoad() {
super.viewDidLoad()
let save = UserDefaults.standard
if save.value(forKey: "Purchase") == nil {
let request = GADRequest()
request.testDevices = [kGADSimulatorID]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "loadAds"), object: nil)
} else {
}
if let view = self.view as! SKView? {
if let scene = SKScene(fileNamed: "GameMenuScene") {
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = false
view.showsNodeCount = false
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
#IBAction func removeAds_TouchUpInside(_ sender: Any) {
let payment = SKPayment(product: product!)
SKPaymentQueue.default().add(payment)
}
override func viewWillLayoutSubviews() {
NotificationCenter.default.addObserver(self, selector: #selector(self.loadAds), name: NSNotification.Name(rawValue: "loadAds"), object: nil)
}
func loadAds() {
self.fullScreenAds = createAndLoadInterstitials()
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override var prefersStatusBarHidden: Bool {
return true
}
func createAndLoadInterstitials() -> GADInterstitial? {
fullScreenAds = GADInterstitial(adUnitID: "ca-app-pub-7212561254132738/9424537183")
guard let fullScreenAds = fullScreenAds else {
return nil
}
let request = GADRequest()
request.testDevices = [kGADSimulatorID]
fullScreenAds.load(request)
fullScreenAds.delegate = self
return fullScreenAds
}
func interstitialDidReceiveAd(_ ad: GADInterstitial) {
print("Ad received")
ad.present(fromRootViewController: self)
}
func interstitialDidFail(toPresentScreen ad: GADInterstitial) {
print("No ad can be loaded")
}
func getPurchaseInfo() {
if SKPaymentQueue.canMakePayments() {
let request = SKProductsRequest(productIdentifiers: NSSet(objects: self.productID) as! Set<String>)
request.delegate = self
request.start()
} else {
print("Enable in app purchase!")
return
}
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
var products = response.products
if (products.count == 0) {
print("Error")
return
} else {
product = products[0]
removeAdsButton.isEnabled = true
print("Succes!")
}
let invalids = response.invalidProductIdentifiers
for product in invalids {
print("Product not found \(product)")
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case SKPaymentTransactionState.purchased:
SKPaymentQueue.default().finishTransaction(transaction)
print("SuccessPayementQeue!")
removeAdsButton.isEnabled = false
let save = UserDefaults.standard
save.set(true, forKey: "Purchase")
save.synchronize()
case SKPaymentTransactionState.failed:
SKPaymentQueue.default().finishTransaction(transaction)
print("Failed transaction state")
default:
break
}
}
}
}
You are facing issue because of your product is nil so you can write code as below for solve it.
#IBAction func removeAds_TouchUpInside(_ sender: Any) {
if let myP = product {
let payment = SKPayment(product: myP)
SKPaymentQueue.default().add(payment)
}
else {
print("Product is not found.")
}
}

Redeem Button for in app purchases

I submitted my app to apple but they rejected it because, there was no redeem button for in app purchases. How can i do that. I made a redeem button and linked it up to the view controller. Where do i go from there?
Here is my code:
class StoreViewController: UIViewController, SKPaymentTransactionObserver, SKProductsRequestDelegate {
#IBOutlet var redeemOutlet: UIButton!
#IBAction func redeem(_ sender: Any) {
}
#IBOutlet var productDescription: UITextView!
var product: SKProduct?
let productID = "com.myCoolAwesomeApp.mjay.noAdvertisements"
#IBOutlet var buyButton: UIButton!
#IBOutlet var returnButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
buyButton.layer.cornerRadius = 5.0
returnButton.layer.cornerRadius = 5.0
buyButton.isEnabled = false
SKPaymentQueue.default().add(self)
getPurchaseInfo()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func purchase(_ sender: Any) {
let payment = SKPayment(product: product!)
SKPaymentQueue.default().add(payment)
}
#IBAction func dismissView(_ sender: Any) {
//self.dismiss(animated: true, completion: nil)
}
func getPurchaseInfo() {
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 Purchases in your settings."
}
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
var products = response.products
if (products.count) == 0 {
productDescription.text = "Product not found."
} else {
product = products[0]
productDescription.text = "Remove all advertisements within the game for $0.99"
buyButton.isEnabled = true
}
let invalids = response.invalidProductIdentifiers
for product in invalids {
print("Product not found: \(product)")
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case SKPaymentTransactionState.purchased:
SKPaymentQueue.default().finishTransaction(transaction)
productDescription.text = "Purchase Successful"
buyButton.isEnabled = false
let save = UserDefaults.standard
save.set(true, forKey: "Purchase")
save.synchronize()
case SKPaymentTransactionState.failed:
SKPaymentQueue.default().finishTransaction(transaction)
productDescription.text = "Purchase Failed. Try again later."
default:
break
}
}
}
}

Why is my array out of bounds?

I am trying to incorporate an in-app purchase. For some reason I am getting a fatal error that states 'fatal error: Index out of range' with an array that holds the SKProduct products. I have one nonconsumable in app purchase, but may decide to add more later. I am trying to access the PREMIUM_PRODUCT_ID product so I can make a purchase. The error occurs when the purchaseMyProduct function is called. Any ideas in why the iapProducts array is out of bounds? Thanks for your help!
The fatal error occurs on this line purchaseMyProduct(product: iapProducts[0])
import UIKit
import StoreKit
class Settings: UIViewController, SKProductsRequestDelegate,
SKPaymentTransactionObserver {
let PREMIUM_PRODUCT_ID = "---------------"
var productID = ""
var productsRequest = SKProductsRequest()
var iapProducts = [SKProduct]()
var nonConsumablePurchaseMade = UserDefaults.standard.bool(forKey: "nonConsumablePurchaseMade")
#IBOutlet weak var adsBtn: UIButton!
#IBAction func restorePurchase(_ sender: Any) {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
// Check your In-App Purchases
print("NON CONSUMABLE PURCHASE MADE: \(nonConsumablePurchaseMade)")
// Fetch IAP Products available
fetchAvailableProducts()
UIAlertView(title: "IAP Tutorial",
message: "You've successfully restored your purchase!",
delegate: nil, cancelButtonTitle: "OK").show()
}
#IBAction func review(_ sender: Any) {
UIApplication.shared.openURL(NSURL(string: "-----------------")! as URL)
}
#IBAction func removeAds(_ sender: Any) {
//UIApplication.shared.openURL(NSURL(string: "----------------")! as URL)
purchaseMyProduct(product: iapProducts[0])
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
...
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
nonConsumablePurchaseMade = true
UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade")
/*UIAlertView(title: "IAP Tutorial",
message: "You've successfully restored your purchase!",
delegate: nil, cancelButtonTitle: "OK").show()*/
}
func fetchAvailableProducts() {
// Put here your IAP Products ID's
let productIdentifiers = NSSet(objects:
PREMIUM_PRODUCT_ID
)
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
productsRequest.delegate = self
productsRequest.start()
}
func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) {
if (response.products.count > 0) {
iapProducts = response.products
// 1st IAP Product (Consumable) ------------------------------------
let firstProduct = response.products[0] as SKProduct
// Get its price from iTunes Connect
let numberFormatter = NumberFormatter()
numberFormatter.formatterBehavior = .behavior10_4
numberFormatter.numberStyle = .currency
numberFormatter.locale = firstProduct.priceLocale
let price1Str = numberFormatter.string(from: firstProduct.price)
// Show its description
//consumableLabel.text = firstProduct.localizedDescription + "\nfor just \(price1Str!)"
// ------------------------------------------------
// 2nd IAP Product (Non-Consumable) ------------------------------
let secondProd = response.products[0] as SKProduct
// Get its price from iTunes Connect
numberFormatter.locale = secondProd.priceLocale
let price2Str = numberFormatter.string(from: secondProd.price)
// Show its description
//nonConsumableLabel.text = secondProd.localizedDescription + "\nfor just \(price2Str!)"
// ------------------------------------
}
}
func canMakePurchases() -> Bool { return SKPaymentQueue.canMakePayments() }
func purchaseMyProduct(product: SKProduct) {
if self.canMakePurchases() {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
print("PRODUCT TO PURCHASE: \(product.productIdentifier)")
productID = product.productIdentifier
// IAP Purchases dsabled on the Device
} else {
UIAlertView(title: "IAP Tutorial",
message: "Purchases are disabled in your device!",
delegate: nil, cancelButtonTitle: "OK").show()
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
if let trans = transaction as? SKPaymentTransaction {
switch trans.transactionState {
case .purchased:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
// The Consumable product (10 coins) has been purchased -> gain 10 extra coins!
if productID == PREMIUM_PRODUCT_ID {
// Save your purchase locally (needed only for Non-Consumable IAP)
nonConsumablePurchaseMade = true
UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade")
//premiumLabel.text = "Premium version PURCHASED!"
UIAlertView(title: "IAP Tutorial",
message: "You've successfully unlocked the Premium version!",
delegate: nil,
cancelButtonTitle: "OK").show()
}
break
case .failed:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
break
case .restored:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
break
default: break
}}}
}
}
Your code is way too long, so I took the liberty of making an educated guess what is relevant:
import UIKit
import StoreKit
class Settings: UIViewController, SKProductsRequestDelegate,
SKPaymentTransactionObserver {
var iapProducts = [SKProduct]()
#IBAction func removeAds(_ sender: Any) {
purchaseMyProduct(product: iapProducts[0])
}
}
When you declare iapProducts, you are creating an empty array that contains no values.
Then, in your removeAds function, you are telling it to do something with the first element.
I am going to take a wild guess that nothing exists, so you are forcing it to access an object that doesn't exist.
Try the following and see if it fixes your issue.
var iapProducts: [SKProduct]?
#IBAction func removeAds(_ sender: Any) {
guard let product = iapProducts?.first else { return }
purchaseMyProduct(product: product)
}
This accomplishes two things:
It makes your [SKProduct] array an optional
The guard statement safely unwraps the first product, so only if one exists, will it try to call your purchaseMyProduct function. Otherwise, it just exits

Resources