Thomas Braun
Thomas Braun

Reputation: 1349

In App Purchase issue with SKProduct() in Swift

I am trying to implement In-App purchases into an app I am creating however when I try to 'get' the product I have var products = [SKProducts]() however this returns an empty array causing the application to crash. I have check off all the tax agreements etc. and when I test it out in apples in app purchase example project the IAPs show up.

The full code where the problem occurs is below.

class Model {

        var products = [SKProduct]()

        func getProduct(containing keyword: String) -> SKProduct? {
            // print("The array of SKProducts in Model getProduct is \(products)")
            // let test = products.filter { $0.productIdentifier.contains(keyword) }.first
            print("The products are: \(products)")
            print(products.filter { $0.productIdentifier.contains(keyword) }.first)
            return products.filter { $0.productIdentifier.contains(keyword) }.first
        }
    }

The print statements return: "The products are: []" and "nil"

If it helps the full project can be found on GitHub here

Upvotes: 0

Views: 2105

Answers (2)

duckoteka
duckoteka

Reputation: 21

To get products you have to make SKProductsRequest and wait for the answer in SKProductsRequestDelegate methods.

Create and start request:

let request = SKProductsRequest(productIdentifiers: ids)
request.delegate = self // self is delegate of SKProductsRequestDelegate, see below
request.start()

Where ids has to be Set<String> of you product identifiers.

Conform to SKProductsRequestDelegate:

extension MyClass: SKProductsRequestDelegate {

    internal func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        print(response.products) // here is your [SKProduct] array
        print(response.invalidProductIdentifiers) // if you put wrong ids they would be here
    } 

    internal func request(_ request: SKRequest, didFailWithError error: Error) {
        print(error) // something wrong
    }
}

Upvotes: 0

Reinhard M&#228;nner
Reinhard M&#228;nner

Reputation: 15217

You function getProduct does nothing else than to print an empty array (products), and to filter this empty array and retrieve the first element (which does not exist).
I suggest to use a helper like this (I copied it from somewhere, don't remember where):

import Foundation
import StoreKit

class IAPHelper: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {

  typealias RequestProductsCompletionHandler = (_ success:Bool, _ products:[SKProduct]?) -> ()
  var completionHandler: RequestProductsCompletionHandler?
  var productsRequest: SKProductsRequest?
  var productIdentifiers: Set<String>
  var purchasedProductIdentifiers: Set<String>?

  // MARK: - Init  

  init(identifiers: Set<String>) {

    // Store product identifiers
    productIdentifiers = identifiers;

    // Check for previously purchased products
    purchasedProductIdentifiers = Set()
    for productIdentifier in productIdentifiers {
      let productPurchased = UserDefaults.standard.bool(forKey: productIdentifier)
      if productPurchased {
        purchasedProductIdentifiers?.insert(productIdentifier)
                print("Previously purchased: \(productIdentifier)");
      } else {
                print("Not purchased: \(productIdentifier)");
      }
    }
    super.init() // must be called after subclass init, but before "self" is used
    SKPaymentQueue.default().add(self)
  } // init

  func requestProductsWithCompletionHandler(_ completionHandler: @escaping RequestProductsCompletionHandler) {
    self.completionHandler = completionHandler

    productsRequest = SKProductsRequest.init(productIdentifiers: productIdentifiers)
    productsRequest!.delegate = self
    productsRequest!.start()
  }

  // MARK: - Products request delegate

  func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    productsRequest = nil

    let skProducts = response.products
        for skProduct in skProducts {
            print("Found product: \(skProduct.productIdentifier), \(skProduct.localizedTitle), \(skProduct.price.floatValue)");
    }

    completionHandler!(true, skProducts)
    completionHandler = nil
  }

  func request(_ request: SKRequest, didFailWithError error: Error) {
        // NOTE: If this happens on the simulator, close the simulator window and re-run the app. This helps normally !!!
        print("Failed to load list of products. Error: \(error)")
    productsRequest = nil

    completionHandler!(false, nil)
    completionHandler = nil
  }

  // MARK: - Purchase

  func productPurchchased(_ productIdentifier:String) -> Bool {
    return purchasedProductIdentifiers!.contains(productIdentifier)
  }

  func buyProduct(_ product:SKProduct) {
        print("Buying \(product.productIdentifier)");

    let payment = SKPayment(product: product)
    SKPaymentQueue.default().add(payment)
  }

  func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    for transaction in transactions {
      switch transaction.transactionState {
      case SKPaymentTransactionState.purchased:
        self.completeTransaction(transaction)

      case SKPaymentTransactionState.failed:
        self.failedTransaction(transaction)

      case SKPaymentTransactionState.restored:
        self.restoreTransaction(transaction)

      default:
        print("Error: Unknown SKPaymentTransactionState")
      }
    }
  }

    func completeTransaction(_ transaction:SKPaymentTransaction) {
        print("completeTransaction...");

        self.provideContentForProductIdentifier(transaction.payment.productIdentifier)
        SKPaymentQueue.default().finishTransaction(transaction)
    }

    func restoreTransaction(_ transaction:SKPaymentTransaction) {
        print("restoreTransaction...");

        self.provideContentForProductIdentifier(transaction.original!.payment.productIdentifier)
        SKPaymentQueue.default().finishTransaction(transaction)
    }

    func failedTransaction(_ transaction:SKPaymentTransaction) {
        print("failedTransaction...");
        if let error = transaction.error as NSError?, error.code == SKError.paymentCancelled.rawValue {
                print("Transaction error:\(error.localizedDescription)");
        }

        SKPaymentQueue.default().finishTransaction(transaction)
    }

    func provideContentForProductIdentifier(_ productIdentifier:String) {

        purchasedProductIdentifiers?.insert(productIdentifier)
    UserDefaults.standard.set(true, forKey:productIdentifier)
    NotificationCenter.default.post(name: Notification.Name(rawValue: IAPHelperProductPurchasedNotification), object: productIdentifier, userInfo: nil)
  }

  func restoreCompletedTransactions() {
    SKPaymentQueue.default().restoreCompletedTransactions()
  }

}

Upvotes: 1

Related Questions