Delayed Load of Products in Store Swift v3

191 views Asked by At

Currently working on submitting my application to Apple, and I'm experiencing a strange issue where my product identifiers are not loading immediately. Sometimes if you load the store quick enough, the prices are not loaded and you can't make a purchase. Apple is reporting that they can't make a purchase on their ipv6 network, but I've also seen this issue on ipv4.

Do you see any logic issues which would be causing this issue and/or ipv6 non-friendly code? Thank you.

Code Follows:

IAPManager:

import Foundation
import StoreKit
import RealmSwift
import AVFoundation

class IAPManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver
{
static let sharedInstance = IAPManager()

private let realm = try! Realm()

// SKRequest
var request:SKProductsRequest!
// Array of SKProducts
var products:[SKProduct] = []

// Audio
var audioPlayer: AVAudioPlayer!

// Received Response From Store
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {

    // Check for Response
    self.products = response.products
}

// Creates String Array of Product Identifiers
func getStoreProductIdentifiers() -> [String]
{
    var identifiers: [String] = []

    let hintsPackXS = "net.identitywithheld.HintsPackXS"

    let hintsPackS = "net.identitywithheld.HintsPackS"

    let hintsPackM = "net.identitywithheld.HintsPackM"

    let hintsPackL = "net.identitywithheld.HintsPackL"

    let hintsPackXL = "net.identitywithheld.HintsPackXL"

    let removeAds = "net.identitywithheld.RemoveAds"

    identifiers.append(hintsPackXS)
    identifiers.append(hintsPackS)
    identifiers.append(hintsPackM)
    identifiers.append(hintsPackL)
    identifiers.append(hintsPackXL)
    identifiers.append(removeAds)

    return identifiers
}

// Perform Request with Identifiers
func performProductRequestForIdentifiers(identifiers:[String]){

    // Create Set Out of String Array and Type Cast as Set<String>
    // Note that sets are not in any paritcular order.
    let products = NSSet(array: identifiers) as! Set<String>

    // Set request to call based on products identifier
    self.request = SKProductsRequest(productIdentifiers: products)

    // Set Delegate to self (this class)
    self.request.delegate = self

    // Start Request
    self.request.start()
}

// Request Store Products
// Gets Identifiers and Performs Requests (Ties Together)
func requestStoreProducts(){
    self.performProductRequestForIdentifiers(identifiers: self.getStoreProductIdentifiers())
}

// Checks to see if user can make purchases
func setupPurchases() -> Bool
{
    // Check to See if Can Make Payments
    if SKPaymentQueue.canMakePayments(){

        // Sets Self As Observer
        SKPaymentQueue.default().add(self)

        return true
    }
    else
    {
        return false
    }
}

// Requests Payment Request
func createPaymentRequestForProduct(product:SKProduct)
{
    let payment = SKMutablePayment(product: product)
    payment.quantity = 1

    // Starts Payment Process
    SKPaymentQueue.default().add(payment)
}

// Payment Observer - Is Updated Whenever There's An Update
//MARK - Transaction Observer
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

    // For Each Transaction in Transaction Array [SKPaymentTransaction]
    for transaction in transactions {
        switch transaction.transactionState{
        case .purchasing:
            print("purchasing")
            break
        case .purchased:
            print("purchased")
            complete(transaction: transaction)
            queue.finishTransaction(transaction) // Marks as Finished to Remove from Queue
            break
        case .deferred:
            print("deferred")
            break
        case .failed:
            print("failed")
            fail(transaction: transaction)
            queue.finishTransaction(transaction) // Marks as Finished to Remove from Queue
            break
        case .restored:
            print("restored")
            restore(transaction: transaction)
            queue.finishTransaction(transaction) // Marks as Finished to Remove from Queue
            break
        }
    }
}

// Complete Purchase
private func complete(transaction: SKPaymentTransaction) {
    print("complete...")
    deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier)

    // Check Whether Sounds Are On
    let gameDataObj = realm.objects(GameData.self).filter("id == 0").first
    let soundOn = gameDataObj!.sound

    if(soundOn == true)
    {
        let hintSound = NSURL(fileURLWithPath: Bundle.main.path(forResource: "purchaseComplete", ofType: "caf")!)
        try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
        try? AVAudioSession.sharedInstance().setActive(true)

        try? audioPlayer = AVAudioPlayer(contentsOf: hintSound as URL)
        audioPlayer.volume = 0.5
        audioPlayer!.prepareToPlay()
        audioPlayer!.play()
    }

    // If On, Play Sound
}


// Restore Purchases
private func restore(transaction: SKPaymentTransaction) {
    guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
    print("restore... \(productIdentifier)")
    deliverPurchaseNotificationFor(identifier: productIdentifier)
    SKPaymentQueue.default().finishTransaction(transaction)
}

private func fail(transaction: SKPaymentTransaction) {
    print("fail...")
    if let transactionError = transaction.error as? NSError {
        if transactionError.code != SKError.paymentCancelled.rawValue {
            print("Transaction Error: \(transaction.error?.localizedDescription)")
        }
    }
    SKPaymentQueue.default().finishTransaction(transaction)
}

private func deliverPurchaseNotificationFor(identifier: String?) {
    guard let identifier = identifier else { return }

    let gameDataObj = realm.objects(GameData.self).filter("id == 0")
    var hints = gameDataObj.first!.hints
    var hintsPurchase = false

    if(identifier == "net.identitywithheld.HintsPackXS")
    {
        print("Extra Small Pack")
        hints = hints + 10
        hintsPurchase = true
    }
    else if(identifier == "net.identitywithheld.HintsPackS")
    {
        print("Small Pack")
        hints = hints + 45
        hintsPurchase = true
    }
    else if(identifier == "net.identitywithheld.HintsPackM")
    {
        print("Medium Pack")
        hints = hints + 125
        hintsPurchase = true
    }
    else if(identifier == "net.identitywithheld.HintsPackL")
    {
        print("Large Pack")
        hints = hints + 200
        hintsPurchase = true
    }
    else if(identifier == "net.identitywithheld.HintsPackXL")
    {
        print("Extra Large Pack")
        hints = hints + 500
        hintsPurchase = true
    }
    else if(identifier == "net.identitywithheld.RemoveAds")
    {
        print("Remove Ads")
        try! realm.write {
            gameDataObj.first!.ads = false
        }
    }

    // If Hint Purchase, Set Hints
    if(hintsPurchase == true)
    {
        try! realm.write {
            gameDataObj.first!.hints = hints
        }
    }
}

func getTextWidth(font: UIFont, text: String) -> CGFloat
{
    let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: 17, height: 17))
    label.text = text
    label.font = font
    label.sizeToFit()

    return label.frame.width
}

func getTextHeight(font: UIFont, text: String, width: CGFloat) -> CGFloat
{
    let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: 17))
    label.text = text
    label.font = font
    label.numberOfLines = 0
    label.sizeToFit()

    return label.frame.height
}
}

App Delegate

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    openRealm()
    // Override point for customization after application launch.
    // Use Firebase library to configure APIs
    FIRApp.configure()
    GADMobileAds.configure(withApplicationID: adMobAppID)

    // Store Call to Get Products if Can Make Purchases

    if (IAPManager.sharedInstance.setupPurchases() == true)
    {
        IAPManager.sharedInstance.requestStoreProducts()

        UserDefaults.standard.set(true, forKey: "IAPCapable")
        UserDefaults.standard.synchronize()
    }
    else
    {
        UserDefaults.standard.set(false, forKey: "IAPCapable")
        UserDefaults.standard.synchronize()
    }

    // Initialize the Chartboost library
    Chartboost.start(withAppId: "identitywithheld", appSignature: "identitywithheld", delegate: nil)

    return true
}
1

There are 1 answers

0
Apocal On BEST ANSWER

After testing this vigorously, I realized that I wasn't delaying the store load and so therefore the products were not fully loaded. This isn't an ipv6 issue.

To resolve this issue, I added an activity indicator and implemented a timer to check and see if the products were loaded before displaying the store.