In app purchases crashing app when changing scenes

Ast*_*rum 5 iphone xcode in-app-purchase ios swift

I've implemented in app purchases into my shop scene for my game and have been having problems when changing from the shop scene to another scene it seems to crash the game and gives me this error

Thread 1: EXC_BAD_ACCESS (code=1, address=0x840f8010)
Run Code Online (Sandbox Code Playgroud)

Or it gives me a multiple version of other errors such as:

Thread 1: EXC_BAD_ACCESS (code=1, address=0x3f8)
Run Code Online (Sandbox Code Playgroud)

it also sometimes gives me an error that changes lines in the subclass like so

enter image description here

When I comment out the code in the subclass that searches for the app purchase information or put the phone into airplane mode it works fine and the problem is gone.

I have a subclass of SKnode that gets the information on the purchasable items and then displays it through the use of SKLabels and sprite nodes to display a picture of the purchase in the shop as seen below:

class InAppPurchaseItems: SKNode, SKProductsRequestDelegate {

var shopItemNode = SKSpriteNode()
var itemPriceBackground = SKSpriteNode()
var shopItemLabel = SKLabelNode()
var shopItemTitleLabel = SKLabelNode()
var pressableNode = SKSpriteNode()
var itemPriceLabel = SKLabelNode()

var title: String = ""
var information: String = ""
var image: String = ""

var price:String = "X"

func createAppPurchaseItem(ID: String, purchaseImage: String, purchaseTitle:String) {

    title = purchaseTitle
    image = purchaseImage

    createTheNode()

    //let product = SKProduct()
    let productID: NSSet = NSSet(objects: ID)  //"RedShield.Astrum.Purchase", "DoubleCoin.Astrum.Purchase")
    let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
    request.delegate = self as? SKProductsRequestDelegate
    request.start()
}

public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    print("product request...")
    let myProduct = response.products
    for product in myProduct {
        print("product added")

        if product.productIdentifier == "RedShield.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            information = product.localizedDescription

        } else if product.productIdentifier == "DoubleCoin.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            information = product.localizedDescription

        } else if product.productIdentifier == "1500Stars.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirOne")

        } else if product.productIdentifier == "7500Stars.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirTwo")

        } else if product.productIdentifier == "14000Stars.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirThree")

        } else if product.productIdentifier == "28000Stars.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirFour")

        } else if product.productIdentifier == "65000Stars.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirFive")

        } else if product.productIdentifier == "128000Stars.Astrum.Purchase" {

            price = priceStringForProduct(item: product)!
            shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirSix")

        }

         createShopLabels()
    }
}

func priceStringForProduct(item: SKProduct) -> String? {
    let price = item.price
    if price == 0 {
        return "GET" //or whatever you like
    } else {
        let numberFormatter = NumberFormatter()
        let locale = item.priceLocale
        numberFormatter.numberStyle = .currency
        numberFormatter.locale = locale
        return numberFormatter.string(from: price)
    }
}

func createTheNode() {

    let tex:SKTexture = SKTexture(imageNamed: image)

    shopItemNode = SKSpriteNode(texture: tex, color: SKColor.black, size: CGSize(width: 85, height: 85)) //frame.maxX / 20, height: frame.maxY / 20))
    shopItemNode.zPosition = -10
    shopItemNode.position = CGPoint(x: 0, y: 35)

    self.addChild(shopItemNode)
    self.name = "ShopItem"
    self.zPosition = -11

    shopItemTitleLabel = SKLabelNode(fontNamed: "Avenir-Black")
    shopItemTitleLabel.fontColor = UIColor.black;
    shopItemTitleLabel.fontSize = 15 //self.frame.maxY/30
    shopItemTitleLabel.position = CGPoint (x: 0, y: -30)
    shopItemTitleLabel.text = "\(title)"
    shopItemTitleLabel.zPosition = -9
    self.addChild(shopItemTitleLabel)

    itemPriceBackground = SKSpriteNode(texture: SKTexture(imageNamed: "PriceShopBackground"), color: .clear, size: CGSize(width: 80, height: 30)) //SKSpriteNode(color: SKColor.black, size: CGSize(width: 65, height: 20))
    //itemPriceBackground.alpha = 0.4
    itemPriceBackground.zPosition = -10
    itemPriceBackground.position = CGPoint(x: 0, y: -54)
    addChild(itemPriceBackground)

    pressableNode = SKSpriteNode(texture: nil, color: .clear, size: CGSize(width: 100, height: 140))
    pressableNode.zPosition = -7
    pressableNode.position = CGPoint(x: 0, y: 0)
    shopItemSprites.append(pressableNode)
    addChild(pressableNode)

}

func createShopLabels() {

    shopItemLabel = SKLabelNode(fontNamed: "Avenir-Black")
    shopItemLabel.fontColor = UIColor.white;
    shopItemLabel.fontSize = 15 //self.frame.maxY/30
    shopItemLabel.position = CGPoint (x: 0, y: -60)
    shopItemLabel.text = "\(price)"
    shopItemLabel.zPosition = -9
    addChild(shopItemLabel)

 }

}
Run Code Online (Sandbox Code Playgroud)

they're then displayed on the store scene with the following code:

 let ShopItem = InAppPurchaseItems()
 ShopItem.createAppPurchaseItem(ID: "DoubleCoin.Astrum.Purchase", purchaseImage: "2StarCoin", purchaseTitle: "+2 In Game Pickups")
 ShopItem.position = CGPoint(x: self.frame.midX / 1.6, y: self.frame.midY * 0.8)
 ShopItem.zPosition = 100
 ShopItem.name = "Shp0"
 moveableArea.addChild(ShopItem)
Run Code Online (Sandbox Code Playgroud)

Shop's Main Class

The shops main class also has in app purchase code that is used for buying the product and also to be able to search for the product information just like in the subclass as seen below

class ShopItemMenu: SKScene, SKProductsRequestDelegate, SKPaymentTransactionObserver {

//Purchase Variables
var listOfProducts = [SKProduct]()
var p = SKProduct()

override func didMoveToView(to view: SKView) {

 let ShopItem = InAppPurchaseItems()
 ShopItem.createAppPurchaseItem(ID: "DoubleCoin.Astrum.Purchase", purchaseImage: "2StarCoin", purchaseTitle: "+2 In Game Pickups")
 ShopItem.position = CGPoint(x: self.frame.midX / 1.6, y: self.frame.midY * 0.8)
 ShopItem.zPosition = 100
 ShopItem.name = "Shp0"
 moveableArea.addChild(ShopItem)

}


//This function allows for a product to be bought buy the user and starts the proccess for purchasing
func appPurchaseBuying(appPurchaseID:String) {
    for product in listOfProducts {
        let prodID = product.productIdentifier
        if(prodID == appPurchaseID) {
            p = product
            buyProduct()
        }
    }
}

//This Function restores all previously purchased Items (use this for the restor button.
func restorePurchasesOfItems() {
    SKPaymentQueue.default().add(self)
    SKPaymentQueue.default().restoreCompletedTransactions()
}


//This function checks if they can make payments and then loads the product ids from a harcoded set. (use this to start when the scene starts)
func checkCanMakePayment() {
    if (SKPaymentQueue.canMakePayments()) {
        print("can make payments...")
        let productID: NSSet = NSSet(objects: "RedShield.Astrum.Purchase", "DoubleCoin.Astrum.Purchase")
        let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
        request.delegate = self
        request.start()
    } else {
        let alert = UIAlertController(title: "In-App Purchases Not Enabled", message: "Please enable In App Purchases in Settings", preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "Settings", style: UIAlertActionStyle.default, handler: { alertAction in
            alert.dismiss(animated: true, completion: nil)

            let url: NSURL? = NSURL(string: UIApplicationOpenSettingsURLString)
            if url != nil
            {
                UIApplication.shared.openURL(url! as URL)
            }

        }))
        alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: { alertAction in
            alert.dismiss(animated: true, completion: nil)
        }))

        if let vc = self.scene?.view?.window?.rootViewController {
            vc.present(alert, animated: true, completion: nil)
        }
    }
}

//This allows the user to buy the product with a product idetifier given by the variable "p"
func buyProduct() {
    print("buying " + p.productIdentifier)
    let pay = SKPayment(product: p)
    SKPaymentQueue.default().add(self)
    SKPaymentQueue.default().add(pay as SKPayment)
}

//This Function gets all the avaliable products from apple and puts them into the product Array called listOfProducts
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
   /* print("product request...")
    let myProduct = response.products
    for product in myProduct {
        print("product added")

        if product.productIdentifier == "RedShield.Astrum.Purchase" {

            shieldPurchasePrice = priceStringForProduct(item: product)!

        } else if product.productIdentifier == "DoubleCoin.Astrum.Purchase" {

            DoubleCoinPurchasePrice = priceStringForProduct(item: product)!
        }

      /*print(product.productIdentifier)
        print(product.localizedTitle)
        print(product.localizedDescription)
        print(product.price)
     */
        listOfProducts.append(product)
    }*/
}




func priceStringForProduct(item: SKProduct) -> String? {
    let price = item.price
    if price == 0 {
        return "GET" //or whatever you like
    } else {
        let numberFormatter = NumberFormatter()
        let locale = item.priceLocale
        numberFormatter.numberStyle = .currency
        numberFormatter.locale = locale
        return numberFormatter.string(from: price)
    }
}



    //This Function restores all the already purchased products so that things can be restored such as shield
    public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
    print("restoring all...")
    for transaction in queue.transactions {
        let t: SKPaymentTransaction = transaction
        let prodID = t.payment.productIdentifier as String

        switch prodID {
        case "RedShield.Astrum.Purchase":
            isRedShieldPurchaseOn = true

            let defaults = UserDefaults.standard
            defaults.set(isRedShieldPurchaseOn, forKey: "shieldPurchase")
            print("finished restoring this purchase")

        case "DoubleCoin.Astrum.Purchase":
            isCoinPurchaseOn = true

            let defaults = UserDefaults.standard
            defaults.set(isCoinPurchaseOn, forKey: "doubleCoinPurchase")
            print("finished restoring this purchase")

        default:
            print("IAP not found")
        }
    }

    alert(title: "Restored", msg: "Purchases were restored")
}

//This Function is run when the user makes a purchase and checks the state of the purchase to make sure it works
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    print("adding payment...")

    for transaction: AnyObject in transactions {
        let trans = transaction as! SKPaymentTransaction
        print(trans.error)

        switch trans.transactionState {
        case .purchased:
            print("buying ok, Unlocking purchase...")
            print(p.productIdentifier)

            let prodID = p.productIdentifier
            switch prodID {

                case "RedShield.Astrum.Purchase":
                    isRedShieldPurchaseOn = true

                    let defaults = UserDefaults.standard
                    defaults.set(isRedShieldPurchaseOn, forKey: "shieldPurchase")
                    print("unlocked Purchase")

                case "DoubleCoin.Astrum.Purchase":
                    isCoinPurchaseOn = true

                    let defaults = UserDefaults.standard
                    defaults.set(isCoinPurchaseOn, forKey: "doubleCoinPurchase")
                    print("unlocked Purchase")

                case "SOME IN APP PURCHASE ID HERE":
                    print("unlocked Purchase")

                default:
                    print("IAP Not found")
            }

            queue.finishTransaction(trans)

        case .failed:
            print("error with payment...")
            queue.finishTransaction(trans)

        default:
            print("Default")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Am I going about doing this the right way or is there a better way to do this and how can I fix the crashing issue i'm having?

EDIT

enter image description here

EDIT 2

enter image description here

EDIT 3

enter image description here

EDIT 4

import Foundation
import SpriteKit
import StoreKit

class PurchaseService {

static let session = PurchaseService()
var products = [SKProduct]()
var p = SKProduct()



//This function allows for a product to be bought buy the user and starts the proccess for purchasing
func appPurchaseBuying(appPurchaseID:String) {
    for product in products {
        let prodID = product.productIdentifier
        if(prodID == appPurchaseID) {
            p = product
            buyProduct()
        }
    }
}

//This Function restores all previously purchased Items (use this for the restor button.
func restorePurchasesOfItems() {
    //SKPaymentQueue.default().add(self)
    SKPaymentQueue.default().restoreCompletedTransactions()
}


//This function checks if they can make payments and then loads the product ids from a harcoded set. (use this to start when the scene starts)
func checkCanMakePayment() {
    if (SKPaymentQueue.canMakePayments()) {
        print("can make payments...")
        let productID: NSSet = NSSet(objects: "RedShield.Astrum.Purchase", "DoubleCoin.Astrum.Purchase")
        let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
        //request.delegate = self
        request.start()
    } else {
        let alert = UIAlertController(title: "In-App Purchases Not Enabled", message: "Please enable In App Purchases in Settings", preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "Settings", style: UIAlertActionStyle.default, handler: { alertAction in
            alert.dismiss(animated: true, completion: nil)

            let url: NSURL? = NSURL(string: UIApplicationOpenSettingsURLString)
            if url != nil
            {
                UIApplication.shared.openURL(url! as URL)
            }

        }))
        alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: { alertAction in
            alert.dismiss(animated: true, completion: nil)
        }))
    }
}

//This allows the user to buy the product with a product idetifier given by the variable "p"
func buyProduct() {
    print("buying " + p.productIdentifier)
    let pay = SKPayment(product: p)
    //SKPaymentQueue.default().add(self)
    SKPaymentQueue.default().add(pay as SKPayment)
}

//This Function gets all the avaliable products from apple and puts them into the product Array called listOfProducts
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    print("product request...")
    let myProduct = response.products
    for product in myProduct {
        print("product added")

        products.append(product)
    }
}




func priceStringForProduct(item: SKProduct) -> String? {
    let price = item.price
    if price == 0 {
        return "GET" //or whatever you like
    } else {
        let numberFormatter = NumberFormatter()
        let locale = item.priceLocale
        numberFormatter.numberStyle = .currency
        numberFormatter.locale = locale
        return numberFormatter.string(from: price)
    }
}



//This Function restores all the already purchased products so that things can be restored such as shield
public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
    print("restoring all...")
    for transaction in queue.transactions {
        let t: SKPaymentTransaction = transaction
        let prodID = t.payment.productIdentifier as String

        switch prodID {
        case "RedShield.Astrum.Purchase":
            isRedShieldPurchaseOn = true

            let defaults = UserDefaults.standard
            defaults.set(isRedShieldPurchaseOn, forKey: "shieldPurchase")
            print("finished restoring this purchase")

        case "DoubleCoin.Astrum.Purchase":
            isCoinPurchaseOn = true

            let defaults = UserDefaults.standard
            defaults.set(isCoinPurchaseOn, forKey: "doubleCoinPurchase")
            print("finished restoring this purchase")

        default:
            print("IAP not found")
        }
    }

    //alert(title: "Restored", msg: "Purchases were restored")
}

//This Function is run when the user makes a purchase and checks the state of the purchase to make sure it works
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    print("adding payment...")

    for transaction: AnyObject in transactions {
        let trans = transaction as! SKPaymentTransaction
        print(trans.error)

        switch trans.transactionState {
        case .purchased:
            print("buying ok, Unlocking purchase...")
            print(p.productIdentifier)

            let prodID = p.productIdentifier
            switch prodID {

            case "RedShield.Astrum.Purchase":
                isRedShieldPurchaseOn = true

                let defaults = UserDefaults.standard
                defaults.set(isRedShieldPurchaseOn, forKey: "shieldPurchase")
                print("unlocked Purchase")

            case "DoubleCoin.Astrum.Purchase":
                isCoinPurchaseOn = true

                let defaults = UserDefaults.standard
                defaults.set(isCoinPurchaseOn, forKey: "doubleCoinPurchase")
                print("unlocked Purchase")

            case "SOME IN APP PURCHASE ID HERE":
                print("unlocked Purchase")

            default:
                print("IAP Not found")
            }

            queue.finishTransaction(trans)

        case .failed:
            print("error with payment...")
            queue.finishTransaction(trans)

        default:
            print("Default")
        }
    }
}

}
Run Code Online (Sandbox Code Playgroud)

And*_* M. 2

将所有 StoreKit 代码都放在游戏场景中会使隔离所遇到的问题变得更加困难。我建议您创建一个新的 swift 文件,让我们将其命名为 PurchasingService,并使用如下静态实例:

class PurchaseService {
      static let session = PurchaseService()
      var products = [SKProduct]()
      // code
}
Run Code Online (Sandbox Code Playgroud)

您可以在这里实现所有与购买相关的逻辑。我通常使用 getPurchases 函数从商店加载可用的购买内容,并从 AppDelegate.swift 文件的应用程序函数中调用它。这可以确保您的购买很早就加载,并且在您需要它们的第一刻就准备好了(因为您创建了一个静态实例,您可以在需要通过PurchaseService.session进行购买时随时引用它...)

要获取价格,您可以使用迭代产品变量并检查产品 ID 的函数:

func price(for productID:String)->Double{
  if products.count>0 {
  for product in products {
    if product.productIdentifier == productID {
      return product.price.doubleValue
    }
  }
 }
}
Run Code Online (Sandbox Code Playgroud)

如果您遵守 SKProductRequestDelegate 协议,则无需有条件地将 self 强制转换为它:

 // unnecessary:  request.delegate = self as? SKProductsRequestDelegate
    request.delegate = self
Run Code Online (Sandbox Code Playgroud)

想知道您是否将 ProductRequest 方法公开,因为当请求返回 SKProductResponse 对象时, self 不再可用。

关于项目中的 Objective-C 代码:我发现您可能正在使用 Firebase(我从控制台消息推断)并且它有一些 Objective-C 的零碎内容。