在应用程序购买时更改场景崩溃应用程序

时间:2023-01-22 15:47:29

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)

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

或者它给了我其他错误的多个版本,例如:

Thread 1: EXC_BAD_ACCESS (code=1, address=0x3f8)

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

它有时会给我一个错误,改变子类中的行,就像这样

在应用程序购买时更改场景崩溃应用程序

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:

我有一个SKnode的子类,它获取有关可购买项目的信息,然后通过使用SKLabels和sprite节点显示它,以显示商店中的购买图片,如下所示:

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)

 }

}

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)

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")
        }
    }
}

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

在应用程序购买时更改场景崩溃应用程序

EDIT 2

在应用程序购买时更改场景崩溃应用程序

EDIT 3

在应用程序购买时更改场景崩溃应用程序

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")
        }
    }
}

}

2 个解决方案

#1


2  

Having all the StoreKit code in your game scene makes it more difficult to isolate the issue you're having. I'd suggest you make a new swift file let's call it PurchaseService with a static instance like this:

将所有StoreKit代码放在游戏场景中会使您更难以找到您遇到的问题。我建议你创建一个新的swift文件,让我们用这样的静态实例称它为PurchaseService:

class PurchaseService {
      static let session = PurchaseService()
      var products = [SKProduct]()
      // code
}

You can implement all your purchasing related logic here. I usually use a getPurchases function to load the available purchases from the store and call it from the application function of the AppDelegate.swift file. This ensures that your purchases get loaded very early and will be ready the first moment you need them (because you make a static instance you can refer to it any time you need to do a purchase through PurchaseService.session...)

您可以在此处实施所有与采购相关的逻辑。我通常使用getPurchases函数从商店加载可用的购买并从AppDelegate.swift文件的应用程序函数调用它。这样可以确保您的购买很早就被加载,并且在您需要的第一时间就准备就绪(因为您创建了一个静态实例,您可以在需要通过PurchaseService.session进行购买时参考它...)

To get the prices you can use a function that iterates through your products variable and checks the product id:

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

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

If you comply to the SKProductRequestDelegate protocol you don't need to conditionally cast self to it:

如果您遵守SKProductRequestDelegate协议,则无需有条件地将自己投射到该协议:

 // unnecessary:  request.delegate = self as? SKProductsRequestDelegate
    request.delegate = self

Wondering if you made the productRequest method public because by the time the request returns the SKProductResponse object self is no longer available.

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

Regarding Objective-C code in your project: I see you might be using Firebase (from your console messages I'm inferring) and it has some objective-c bits and pieces.

关于项目中的Objective-C代码:我看到你可能正在使用Firebase(来自我推测的控制台消息),它有一些客观的c部分。

#2


1  

Stab in the dark here, but you've spelled Tier incorrectly in your image names... Any possibility it's crashing because no image exists with those names?

在这里黑暗刺伤,但你在你的图像名称拼写错误的Tier ...任何可能它崩溃,因为这些名称不存在图像?

i.e. this line: shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirSix")

即此行:shopItemNode.texture = SKTexture(imageNamed:“BuyStarsTeirSix”)

ought to be changed to: shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTierSix")

应该更改为:shopItemNode.texture = SKTexture(imageNamed:“BuyStarsTierSix”)

If this isn't your issue I'd recommend you fix the asset names regardless, typos in names gets confusing.

如果这不是您的问题,我建议您修改资产名称,名称中的拼写错误会让您感到困惑。

#1


2  

Having all the StoreKit code in your game scene makes it more difficult to isolate the issue you're having. I'd suggest you make a new swift file let's call it PurchaseService with a static instance like this:

将所有StoreKit代码放在游戏场景中会使您更难以找到您遇到的问题。我建议你创建一个新的swift文件,让我们用这样的静态实例称它为PurchaseService:

class PurchaseService {
      static let session = PurchaseService()
      var products = [SKProduct]()
      // code
}

You can implement all your purchasing related logic here. I usually use a getPurchases function to load the available purchases from the store and call it from the application function of the AppDelegate.swift file. This ensures that your purchases get loaded very early and will be ready the first moment you need them (because you make a static instance you can refer to it any time you need to do a purchase through PurchaseService.session...)

您可以在此处实施所有与采购相关的逻辑。我通常使用getPurchases函数从商店加载可用的购买并从AppDelegate.swift文件的应用程序函数调用它。这样可以确保您的购买很早就被加载,并且在您需要的第一时间就准备就绪(因为您创建了一个静态实例,您可以在需要通过PurchaseService.session进行购买时参考它...)

To get the prices you can use a function that iterates through your products variable and checks the product id:

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

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

If you comply to the SKProductRequestDelegate protocol you don't need to conditionally cast self to it:

如果您遵守SKProductRequestDelegate协议,则无需有条件地将自己投射到该协议:

 // unnecessary:  request.delegate = self as? SKProductsRequestDelegate
    request.delegate = self

Wondering if you made the productRequest method public because by the time the request returns the SKProductResponse object self is no longer available.

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

Regarding Objective-C code in your project: I see you might be using Firebase (from your console messages I'm inferring) and it has some objective-c bits and pieces.

关于项目中的Objective-C代码:我看到你可能正在使用Firebase(来自我推测的控制台消息),它有一些客观的c部分。

#2


1  

Stab in the dark here, but you've spelled Tier incorrectly in your image names... Any possibility it's crashing because no image exists with those names?

在这里黑暗刺伤,但你在你的图像名称拼写错误的Tier ...任何可能它崩溃,因为这些名称不存在图像?

i.e. this line: shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTeirSix")

即此行:shopItemNode.texture = SKTexture(imageNamed:“BuyStarsTeirSix”)

ought to be changed to: shopItemNode.texture = SKTexture(imageNamed: "BuyStarsTierSix")

应该更改为:shopItemNode.texture = SKTexture(imageNamed:“BuyStarsTierSix”)

If this isn't your issue I'd recommend you fix the asset names regardless, typos in names gets confusing.

如果这不是您的问题,我建议您修改资产名称,名称中的拼写错误会让您感到困惑。