In applications trading applications when changing scenes

I implemented app purchases into my store scene for my game, and I had problems moving from the store scene to another scene. This seems to be a game crash and gives me this error.

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

Or it gives me several versions 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 a subclass, for example:

enter image description here

When I comment on the code in a subclass that searches for information about buying an application or puts the phone in airplane mode, it works fine and the problem goes away.

I have a SKnode subclass that retrieves information about purchased items and then displays it using SKLabels and sprite nodes to display an image of a purchase in a store, as shown 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) } } 

they are then displayed on the store scene using 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) 

The main class of the store

The main class of stores also has an application purchase code, which is used to purchase a product, as well as to search for product information, as in a subclass, as shown 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 to do it right, or is there a better way to do this, and how can I fix the error I have?

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") } } } } 
+5
source share
2 answers

Having all the StoreKit code in your game scene makes it difficult to isolate the problem you are facing. I suggest you create a new fast file by calling it in a PurchaseService with a static instance like this:

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

Here you can implement all your logic related to the purchase. I usually use the getPurchases function to load available purchases from the store and call from the application function from the AppDelegate.swift file. This ensures that your purchases are downloaded very early and will be ready the first time you need them (since you are making a static instance, you can reference it anytime you need to make a purchase through PurchaseService.session ...)

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

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

If you are executing the SKProductRequestDelegate protocol, you do not need to conditionally use it for yourself:

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

It's amazing if you made the productRequest method open, because by the time the request returns, the SKProductResponse object is no longer available.

As for the Objective-C code in your project: I see that you can use Firebase (from your console messages that I output), and it has several Objective-C bits and parts.

+2
source

Draw in the dark here, but you incorrectly indicated Tier in your image names ... Any possibility of failure because the image with these names does not exist?

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

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

If this is not your problem, I would recommend that you correct the names of the assets regardless of the fact that typos in the names are confused.

+1
source

Source: https://habr.com/ru/post/1273135/


All Articles