Ast*_*rum 5 iphone xcode in-app-purchase ios swift
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)
Run Code Online (Sandbox Code Playgroud)
Or it gives me a multiple version of other errors such as:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x3f8)
Run Code Online (Sandbox Code Playgroud)
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:
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)
}
}
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)
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")
}
}
}
Run Code Online (Sandbox Code Playgroud)
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")
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
将所有 StoreKit 代码都放在游戏场景中会使隔离所遇到的问题变得更加困难。我建议您创建一个新的 swift 文件,让我们将其命名为 PurchasingService,并使用如下静态实例:
class PurchaseService {
static let session = PurchaseService()
var products = [SKProduct]()
// code
}
Run Code Online (Sandbox Code Playgroud)
您可以在这里实现所有与购买相关的逻辑。我通常使用 getPurchases 函数从商店加载可用的购买内容,并从 AppDelegate.swift 文件的应用程序函数中调用它。这可以确保您的购买很早就加载,并且在您需要它们的第一刻就准备好了(因为您创建了一个静态实例,您可以在需要通过PurchaseService.session进行购买时随时引用它...)
要获取价格,您可以使用迭代产品变量并检查产品 ID 的函数:
func price(for productID:String)->Double{
if products.count>0 {
for product in products {
if product.productIdentifier == productID {
return product.price.doubleValue
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果您遵守 SKProductRequestDelegate 协议,则无需有条件地将 self 强制转换为它:
// unnecessary: request.delegate = self as? SKProductsRequestDelegate
request.delegate = self
Run Code Online (Sandbox Code Playgroud)
想知道您是否将 ProductRequest 方法公开,因为当请求返回 SKProductResponse 对象时, self 不再可用。
关于项目中的 Objective-C 代码:我发现您可能正在使用 Firebase(我从控制台消息推断)并且它有一些 Objective-C 的零碎内容。
| 归档时间: |
|
| 查看次数: |
234 次 |
| 最近记录: |