I'm running into some confusing behavior with my in-app purchases restore feature. Currently, I have the restore feature linked to a button, it seems to crash when I activate it multiple times. For example, if I hit it, restore, navigate to another view, then back to hit the restore again it will crash.
Can anyone check my code and see if I'm missing something staring me in the face?
import SpriteKit
import StoreKit
class PurchaseView: SKScene, SKPaymentTransactionObserver, SKProductsRequestDelegate{
var instructLabel = SKLabelNode()
var priceLabel = SKLabelNode()
var saleBadgeIcon = SKSpriteNode()
var backIcon = SKSpriteNode()
var restoreIcon = SKSpriteNode()
var blueDiceDemo = SKSpriteNode()
var redDiceDemo = SKSpriteNode()
var greenDiceDemo = SKSpriteNode()
var grayDiceDemo = SKSpriteNode()
var bluePID: String = "dice.blue.add"
var redPID: String = "dice.red.add"
var greenPID: String = "dice.green.add"
var grayPID: String = "dice.gray.add"
private var request : SKProductsRequest!
private var products : [SKProduct] = []
private var blueDicePurchased : Bool = false
private var redDicePurchased : Bool = false
private var greenDicePurchased : Bool = false
private var grayDicePurchased : Bool = false
override func didMoveToView(view: SKView) {
// In-App Purchase
initInAppPurchases()
/*
checkAndActivateGreenColor()
checkAndActivateRedColor()
checkAndActivateGrayColor()
checkAndActivateBlueColor()
*/
createInstructionLabel()
createBackIcon()
createRestoreIcon()
createBlueDicePurchase()
createRedDicePurchase()
createGreenDicePurchase()
createGrayDicePurchase()
checkAndActivateDiceColor(bluePID)
checkAndActivateDiceColor(redPID)
checkAndActivateDiceColor(greenPID)
checkAndActivateDiceColor(grayPID)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
for touch in touches {
let location = touch.locationInNode(self)
let node = nodeAtPoint(location)
if (node == backIcon) {
let gameScene = GameScene(size: self.size)
let transition = SKTransition.doorsCloseVerticalWithDuration(0.5)
gameScene.scaleMode = SKSceneScaleMode.ResizeFill
gameScene.backgroundColor = SKColor.whiteColor()
self.scene!.view?.presentScene(gameScene, transition: transition)
} else if (node == restoreIcon) {
print("restore my purchases")
let alert = UIAlertController(title: "Restore Purchases", message: "", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Restore", style: UIAlertActionStyle.Default) { _ in
self.restorePurchasedProducts()
})
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default) { _ in
})
// Show the alert
self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
//restorePurchasedProducts()
} else if (node == blueDiceDemo) {
print("buy blue")
if (!blueDicePurchased) {
inAppPurchase(blueDicePurchased, pid: bluePID)
}
} else if (node == redDiceDemo) {
print("buy red")
if (!redDicePurchased) {
inAppPurchase(redDicePurchased, pid: redPID)
}
} else if (node == greenDiceDemo) {
print("buy green")
if (!greenDicePurchased) {
inAppPurchase(greenDicePurchased, pid: greenPID)
}
} else if (node == grayDiceDemo) {
print("buy gray")
if (!grayDicePurchased) {
inAppPurchase(grayDicePurchased, pid: grayPID)
}
}
}
}
func createBlueDicePurchase() {
blueDiceDemo = SKSpriteNode(imageNamed: "dice1_blue")
blueDiceDemo.setScale(0.6)
blueDiceDemo.position = CGPoint(x: CGRectGetMidX(self.frame) + blueDiceDemo.size.width * 2, y: CGRectGetMidY(self.frame))
addChild(blueDiceDemo)
createSaleBadge(blueDiceDemo)
}
func createGrayDicePurchase() {
grayDiceDemo = SKSpriteNode(imageNamed: "dice1_gray")
grayDiceDemo.setScale(0.6)
grayDiceDemo.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
addChild(grayDiceDemo)
createSaleBadge(grayDiceDemo)
}
func createRedDicePurchase() {
redDiceDemo = SKSpriteNode(imageNamed: "dice1_red")
redDiceDemo.setScale(0.6)
redDiceDemo.position = CGPoint(x: CGRectGetMidX(self.frame) - blueDiceDemo.size.width * 2, y: CGRectGetMidY(self.frame))
addChild(redDiceDemo)
createSaleBadge(redDiceDemo)
}
func createGreenDicePurchase() {
greenDiceDemo = SKSpriteNode(imageNamed: "dice1_green")
greenDiceDemo.setScale(0.6)
greenDiceDemo.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame) - blueDiceDemo.size.height * 1.5)
addChild(greenDiceDemo)
createSaleBadge(greenDiceDemo)
}
func createInstructionLabel() {
instructLabel = SKLabelNode(fontNamed: "Helvetica")
instructLabel.text = "Click item to purchase!"
instructLabel.fontSize = 24
instructLabel.fontColor = SKColor.blackColor()
instructLabel.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMaxY(self.frame) - 50)
addChild(instructLabel)
}
func createPurchasedLabel(node: SKSpriteNode) {
let purchasedLabel = SKLabelNode(fontNamed: "Helvetica")
purchasedLabel.text = "purchased"
purchasedLabel.fontSize = 30
purchasedLabel.zPosition = 2
purchasedLabel.fontColor = SKColor.blackColor()
purchasedLabel.position = CGPoint(x: 0, y: -7.5)
node.addChild(purchasedLabel)
}
func createRestoreIcon() {
restoreIcon = SKSpriteNode(imageNamed: "download")
restoreIcon.setScale(0.4)
restoreIcon.position = CGPoint(x: CGRectGetMinX(self.frame) + 30, y: CGRectGetMinY(self.frame) + 30)
addChild(restoreIcon)
}
func createBackIcon() {
backIcon = SKSpriteNode(imageNamed: "remove")
backIcon.setScale(0.5)
backIcon.position = CGPoint(x: CGRectGetMaxX(self.frame) - 30, y: CGRectGetMinY(self.frame) + 30)
addChild(backIcon)
}
func createSaleBadge(node: SKSpriteNode) {
saleBadgeIcon = SKSpriteNode(imageNamed: "badge")
saleBadgeIcon.setScale(0.4)
saleBadgeIcon.zPosition = 2
saleBadgeIcon.position = CGPoint(x: node.size.width/2, y: node.size.height/2)
node.addChild(saleBadgeIcon)
}
func inAppPurchase(dicePurchased: Bool, pid: String) {
let alert = UIAlertController(title: "In-App Purchases", message: "", preferredStyle: UIAlertControllerStyle.Alert)
// Add an alert action for each available product
for (var i = 0; i < products.count; i++) {
let currentProduct = products[i]
if (currentProduct.productIdentifier == pid && !dicePurchased) {
// Get the localized price
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = .CurrencyStyle
numberFormatter.locale = currentProduct.priceLocale
// Add the alert action
alert.addAction(UIAlertAction(title: currentProduct.localizedTitle + " " + numberFormatter.stringFromNumber(currentProduct.price)!, style: UIAlertActionStyle.Default) { _ in
// Perform the purchase
self.buyProduct(currentProduct)
})
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Default) { _ in
})
// Show the alert
self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}
}
}
//Initializes the App Purchases
func initInAppPurchases() {
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
// Get the list of possible purchases
if self.request == nil {
self.request = SKProductsRequest(productIdentifiers: Set(["dice.green.add", "dice.blue.add", "dice.gray.add","dice.red.add"]))
self.request.delegate = self
self.request.start()
}
}
// Request a purchase
func buyProduct(product: SKProduct) {
let payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
// Restore purchases
func restorePurchasedProducts() {
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
// StoreKit protocoll method. Called when the AppStore responds
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
self.products = response.products
self.request = nil
}
// StoreKit protocoll method. Called when an error happens in the communication with the AppStore
func request(request: SKRequest, didFailWithError error: NSError) {
print(error)
self.request = nil
}
// StoreKit protocoll method. Called after the purchase
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch (transaction.transactionState) {
case .Purchased:
if transaction.payment.productIdentifier == "dice.green.add" {
handleDiceColorPurchase(greenPID)
print("buying green")
} else if transaction.payment.productIdentifier == "dice.blue.add" {
handleDiceColorPurchase(bluePID)
print("buying blue")
} else if transaction.payment.productIdentifier == "dice.red.add" {
handleDiceColorPurchase(redPID)
print("buying red")
} else if transaction.payment.productIdentifier == "dice.gray.add" {
handleDiceColorPurchase(grayPID)
print("buying gray")
} else {
print("Error: Invalid Product ID")
}
queue.finishTransaction(transaction)
case .Restored:
if transaction.payment.productIdentifier == "dice.green.add" {
handleDiceColorPurchase(greenPID)
print("restoring green")
} else if transaction.payment.productIdentifier == "dice.blue.add" {
handleDiceColorPurchase(bluePID)
print("restoring blue")
} else if transaction.payment.productIdentifier == "dice.red.add" {
handleDiceColorPurchase(redPID)
print("restoring red")
} else if transaction.payment.productIdentifier == "dice.gray.add" {
handleDiceColorPurchase(grayPID)
print("restoring gray")
} else {
print("Error: Invalid Product ID")
}
queue.finishTransaction(transaction)
case .Failed:
print("Payment Error: \(transaction.error)")
queue.finishTransaction(transaction)
default:
print("Transaction State: \(transaction.transactionState)")
}
}
}
// Called after the purchase to provide the colored dice feature
func handleDiceColorPurchase(pid: String){
switch(pid) {
case greenPID:
greenDicePurchased = true
greenDiceDemo.alpha = 0.25
greenDiceDemo.removeAllChildren()
createPurchasedLabel(greenDiceDemo)
case redPID:
redDicePurchased = true
redDiceDemo.alpha = 0.25
redDiceDemo.removeAllChildren()
createPurchasedLabel(redDiceDemo)
case grayPID:
grayDicePurchased = true
grayDiceDemo.alpha = 0.25
grayDiceDemo.removeAllChildren()
createPurchasedLabel(grayDiceDemo)
case bluePID:
blueDicePurchased = true
blueDiceDemo.alpha = 0.25
blueDiceDemo.removeAllChildren()
createPurchasedLabel(blueDiceDemo)
default:
print("No action taken, incorrect PID")
}
checkAndActivateDiceColor(pid)
// persist the purchase locally
NSUserDefaults.standardUserDefaults().setBool(true, forKey: pid)
}
func checkAndActivateDiceColor(pid: String){
if NSUserDefaults.standardUserDefaults().boolForKey(pid) {
switch(pid) {
case greenPID:
greenDicePurchased = true
greenDiceDemo.alpha = 0.25
greenDiceDemo.removeAllChildren()
createPurchasedLabel(greenDiceDemo)
case redPID:
redDicePurchased = true
redDiceDemo.alpha = 0.25
redDiceDemo.removeAllChildren()
createPurchasedLabel(redDiceDemo)
case grayPID:
grayDicePurchased = true
grayDiceDemo.alpha = 0.25
grayDiceDemo.removeAllChildren()
createPurchasedLabel(grayDiceDemo)
case bluePID:
blueDicePurchased = true
blueDiceDemo.alpha = 0.25
blueDiceDemo.removeAllChildren()
createPurchasedLabel(blueDiceDemo)
default:
print("No action taken, incorrect PID")
}
}
}
}
When it crashes, there isn't much info that I can decipher. I get an error
stating EXC_BAD_ACCESS (code=1, address=0xc)
on my AppDelegate class and something highlighted green stating Enqueued from
com.apple.root.default-qos.overcommit(Thread 4)
Any help is appreciated!