Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In app purchases crashing app when changing scenes

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

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)

 }

}

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

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

}
like image 815
Astrum Avatar asked Oct 18 '22 03:10

Astrum


1 Answers

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:

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...)

To get the prices you can use a function that iterates through your products variable and checks the product 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:

 // 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.

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.

like image 151
Andras M. Avatar answered Oct 21 '22 02:10

Andras M.