Steve Steve - 6 months ago 75
Swift Question

SKProductRequestDelegate Crashing when leaving view

I have implemented an IAP Scene

OptionsPage: SKScene
where users can purchase "Remove Ads" and in game currency etc. But if I go from
MenuScene: SKScene
to the
OptionsPage: SKScene
and back to the menu quickly it crashes with
Thread 1: EXC_BAD_ACCESS (code=1, address=0x216d0716)
and the only thing in the log down the bottom is (lldb).

I am calling
SKPaymentQueue.defaultQueue().removeTransactionObserver(self)
in
willMoveFromView()
.

In my
didMoveToView
I call the
setIAP()
function (shown below).

After constantly bashing my head against a table (first time dealing with IAP) , I think that it is happening because I've set
request.delegate = self
and so when I enter the scene
request.start()
runs but because I quickly leave the scene
request.delegate = self
is no longer valid.

I thought removing the transaction observer would deal with this in
willMoveFromView
. How can I get around this problem?

Here is how I am calling the purchase in
touchesBegan()


if removeAds.containsPoint(location) {
for product in list {
let prodID = product.productIdentifier
if (prodID == "removeAds") {
p = product
buyProduct()
break
}
}
}


Here is how the IAP section looks in

// MARK: In App Purchases

func setIAP() {
// Set IAPS
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID:NSSet = NSSet(objects: "removeAds, 10000coins")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
}

var list = [SKProduct]()
var p = SKProduct()

func buyProduct() {
print("buy " + p.productIdentifier)
let pay = SKPayment(product: p)
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().addPayment(pay as SKPayment)
}

func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
print("product request")
let myProduct = response.products

for product in myProduct {
print("Product added: \(product.productIdentifier), \(product.localizedTitle), \(product.localizedDescription), \(product.price)")

list.append(product)
}
}

func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {

print("transactions restored")
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction

let prodID = t.payment.productIdentifier as String

switch prodID {
case "removeAds":
defaults.setBool(true, forKey: "removeAdsPurchased")

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

let alert = UIAlertController(title: "Thank You", message: "Thankyou for your purchase.", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.view?.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}

func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("add paymnet")

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

switch trans.transactionState {

case .Purchased, .Restored:
print(p.productIdentifier)

let prodID = p.productIdentifier as String

switch prodID {
case "removeAds":

defaults.setBool(true, forKey: "removeAdsPurchased")

case "10000Coins":

defaults.setInteger(bank + 10000, forKey: "bankValue")
bank = defaults.integerForKey("bankValue")

default:
print("IAP not setup")
}

queue.finishTransaction(trans)
break;

case .Failed:
print("buy error")
queue.finishTransaction(trans)
break;

default:
print("default")
break;
}
}
}

func finishTransaction(trans:SKPaymentTransaction) {
print("Transaction finished")
}

func paymentQueue(queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
print("Transaction removed")
}

Answer

The way I solved this, seeing as there was no feedback, was very tedious but as follows:

Rather than have two scenes I combined the Menu and options scene so that when the options button is tapped, it runs an action to slide the menu nodes off the screen and options onto the screen. This way the request.delegate = self doesn't change therefore avoiding the crash. Then the only issue was if I launch the app and press the play button quickly to move to the game scene before the request.start finishes it would get the same crash. So I created a loading splash screen to run for 2 seconds in the menu scenes didMoveToView this way it had plenty of time to get all the data before allowing interaction.

I would love to hear of a more simple solution but with the little experience I have this is what I came up with.

Comments