WokerHead
WokerHead

Reputation: 967

StoreKit: Error in converting from Swift 1.2 to Swift 3

I got a sample project to learn how to work with Apple's StoreKit so I can learn to apply auto-renewable subscription service to my app and any in app service in general.

Problem is that sample project came in Swift 1.2 and in converting it to Swift 3, I came about to several errors but I got stuck in 2 warnings and 2 errors. Hopefully someone can help me out.

Also, in converting the code to Swift 3 will the code still work? Since it is old? Did in app purchases change in any major way?

Code with Warnings and Errors

func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {

    var products = response.products

    if (products.count != 0) {
        for i in 0 ..< products.count
        {
            self.product = products[i] as? SKProduct
            self.productsArray.append(product!)
        }
        self.tableView.reloadData()
    } else {
        print("No products found")
    }

    products = response.invalidProductIdentifiers

    for product in products
    {
        print("Product not found: \(product)")
    }
}

func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
    print("Transactions Restored")

    var purchasedItemIDS = []
    for transaction:SKPaymentTransaction in queue.transactions {

        if transaction.payment.productIdentifier == "com.brianjcoleman.testiap1"
        {
            print("Consumable Product Purchased")
            // Unlock Feature
        }
        else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap2"
        {
            print("Non-Consumable Product Purchased")
            // Unlock Feature
        }
        else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap3"
        {
            print("Auto-Renewable Subscription Product Purchased")
            // Unlock Feature
        }
        else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap4"
        {
            print("Free Subscription Product Purchased")
            // Unlock Feature
        }
        else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap5"
        {
            print("Non-Renewing Subscription Product Purchased")
            // Unlock Feature
        }
    }

    let alert = UIAlertView(title: "Thank You", message: "Your purchase(s) were restored.", delegate: nil, cancelButtonTitle: "OK")
    alert.show()
}

second error

first error

Rest of code for Store Kit

var tableView = UITableView()
let productIdentifiers = Set(["com.brianjcoleman.testiap1", "com.brianjcoleman.testiap2", "com.brianjcoleman.testiap3", "com.brianjcoleman.testiap4", "com.brianjcoleman.testiap5"])
var product: SKProduct?
var productsArray = Array<SKProduct>()

func requestProductData()
{
    if SKPaymentQueue.canMakePayments() {
        let request = SKProductsRequest(productIdentifiers:
            self.productIdentifiers as Set<String>)
        request.delegate = self
        request.start()
    } else {
        let alert = UIAlertController(title: "In-App Purchases Not Enabled", message: "Please enable In App Purchase in Settings", preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "Settings", style: UIAlertActionStyle.default, handler: { alertAction in
            alert.dismiss(animated: true, completion: nil)

            let url: URL? = URL(string: UIApplicationOpenSettingsURLString)
            if url != nil
            {
                UIApplication.shared.openURL(url!)
            }

        }))
        alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: { alertAction in
            alert.dismiss(animated: true, completion: nil)
        }))
        self.present(alert, animated: true, completion: nil)
    }
}

func buyProduct(_ sender: UIButton) {
    let payment = SKPayment(product: productsArray[sender.tag])
    SKPaymentQueue.default().add(payment)
}

func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

    for transaction in transactions {

        switch transaction.transactionState {

        case SKPaymentTransactionState.purchased:
            print("Transaction Approved")
            print("Product Identifier: \(transaction.payment.productIdentifier)")
            self.deliverProduct(transaction)
            SKPaymentQueue.default().finishTransaction(transaction)

        case SKPaymentTransactionState.failed:
            print("Transaction Failed")
            SKPaymentQueue.default().finishTransaction(transaction)
        default:
            break
        }
    }
}

func deliverProduct(_ transaction:SKPaymentTransaction) {

    if transaction.payment.productIdentifier == "com.brianjcoleman.testiap1"
    {
        print("Consumable Product Purchased")
        // Unlock Feature
    }
    else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap2"
    {
        print("Non-Consumable Product Purchased")
        // Unlock Feature
    }
    else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap3"
    {
        print("Auto-Renewable Subscription Product Purchased")
        // Unlock Feature
    }
    else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap4"
    {
        print("Free Subscription Product Purchased")
        // Unlock Feature
    }
    else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap5"
    {
        print("Non-Renewing Subscription Product Purchased")
        // Unlock Feature
    }
}

func restorePurchases(_ sender: UIButton) {
    SKPaymentQueue.default().add(self)
    SKPaymentQueue.default().restoreCompletedTransactions()
}

TableView

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    let cellFrame = CGRect(x: 0, y: 0, width: self.tableView.frame.width, height: 52.0)
    let retCell = UITableViewCell(frame: cellFrame)

    if self.productsArray.count != 0
    {
        if indexPath.row == 5
        {
            let restoreButton = UIButton(frame: CGRect(x: 10.0, y: 10.0, width: UIScreen.main.bounds.width - 20.0, height: 44.0))
            restoreButton.titleLabel!.font = UIFont (name: "HelveticaNeue-Bold", size: 20)
            restoreButton.addTarget(self, action: #selector(ViewController.restorePurchases(_:)), for: UIControlEvents.touchUpInside)
            restoreButton.backgroundColor = UIColor.black
            restoreButton.setTitle("Restore Purchases", for: UIControlState())
            retCell.addSubview(restoreButton)
        }
        else
        {
            let singleProduct = productsArray[indexPath.row]

            let titleLabel = UILabel(frame: CGRect(x: 10.0, y: 0.0, width: UIScreen.main.bounds.width - 20.0, height: 25.0))
            titleLabel.textColor = UIColor.black
            titleLabel.text = singleProduct.localizedTitle
            titleLabel.font = UIFont (name: "HelveticaNeue", size: 20)
            retCell.addSubview(titleLabel)

            let descriptionLabel = UILabel(frame: CGRect(x: 10.0, y: 10.0, width: UIScreen.main.bounds.width - 70.0, height: 40.0))
            descriptionLabel.textColor = UIColor.black
            descriptionLabel.text = singleProduct.localizedDescription
            descriptionLabel.font = UIFont (name: "HelveticaNeue", size: 12)
            retCell.addSubview(descriptionLabel)

            let buyButton = UIButton(frame: CGRect(x: UIScreen.main.bounds.width - 60.0, y: 5.0, width: 50.0, height: 20.0))
            buyButton.titleLabel!.font = UIFont (name: "HelveticaNeue", size: 12)
            buyButton.tag = indexPath.row
            buyButton.addTarget(self, action: #selector(ViewController.buyProduct(_:)), for: UIControlEvents.touchUpInside)
            buyButton.backgroundColor = UIColor.black
            let numberFormatter = NumberFormatter()
            numberFormatter.numberStyle = .currency
            numberFormatter.locale = Locale.current
            buyButton.setTitle(numberFormatter.string(from: singleProduct.price), for: UIControlState())
            retCell.addSubview(buyButton)
        }
    }

    return retCell
}

Upvotes: 1

Views: 218

Answers (1)

Kevin
Kevin

Reputation: 56129

Both issues in the first function come from the fact that before Swift 3, NSArrays were imported without their generic type (i.e. [Any], rather than [SKProduct]).

Simply getting rid of the as? SKProduct part would fix the warning, but it would be cleaner to just add all the contents in one call:

// Old
for i in 0 ..< products.count
{
    self.product = products[i] as? SKProduct
    self.productsArray.append(product!)
}

// New:
productsArray.append(contentsOf: products)

The error is because while both response.products and response.invalidProductIdentifiers were imported as [Any] originally, they are now typed ([SKProduct] and [String]). The easiest solution is just to use the array directly:

// Old:
products = response.invalidProductIdentifiers

for product in products

// New:
for product in response.invalidProductIdentifiers

Since it's only printing, I'd probably just print the array directly.

The error in the second function is because the compiler needs to know what type of array the variable should be. From the name, I'd guess it was intended to be a [String], but it's not used (as the warning on the same line indicates), so you may as well just remove the line.


The complete updated/modernized/uniform-styled view controller:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, SKProductsRequestDelegate, SKPaymentTransactionObserver {

    enum Product: String {
        case test1 = "com.brianjcoleman.testiap1"
        case test2 = "com.brianjcoleman.testiap2"
        case test3 = "com.brianjcoleman.testiap3"
        case test4 = "com.brianjcoleman.testiap4"
        case test5 = "com.brianjcoleman.testiap5"

        static var allValues: [Product] {
             return [.test1, .test2, .test3, .test4, .test5]
        }
    }

    let tableView = UITableView()
    var productsArray = [SKProduct]()

    override func viewDidLoad()
    {
        super.viewDidLoad()

        tableView.frame = self.view.frame

        tableView.separatorColor = .clear

        tableView.dataSource = self
        tableView.delegate = self

        self.view.addSubview(tableView)

        SKPaymentQueue.default().add(self)
        self.requestProductData()
    }

    override func viewWillDisappear(_ animated: Bool)
    {
        super.viewWillDisappear(animated)

        SKPaymentQueue.default().remove(self)
    }

    // In-App Purchase Methods

    func requestProductData()
    {
        if SKPaymentQueue.canMakePayments() {
            let productIdentifiers = Set(Product.allValues.map { $0.rawValue })
            let request = SKProductsRequest(productIdentifiers: productIdentifiers)
            request.delegate = self
            request.start()
        } else {
            let alert = UIAlertController(title: "In-App Purchases Not Enabled",
                                          message: "Please enable In App Purchase in Settings",
                                          preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Settings", style: .default, handler: { _ in
                alert.dismiss(animated: true, completion: nil)

                if let url = URL(string: UIApplicationOpenSettingsURLString) {
                    UIApplication.shared.openURL(url)
                }
            }))
            alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { _ in
                alert.dismiss(animated: true, completion: nil)
            }))
            self.present(alert, animated: true, completion: nil)
        }
    }

    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse)
    {
        let products = response.products

        if products.count != 0 {
            productsArray.append(contentsOf: products)
            self.tableView.reloadData()
        } else {
            print("No products found")
        }

        let invalidIdentifiers = response.invalidProductIdentifiers
        if invalidIdentifiers.count > 0 {
            print("Invalid product identifiers: \(invalidIdentifiers)")
        }
    }

    func buyProduct(_ sender: UIButton)
    {
        let payment = SKPayment(product: productsArray[sender.tag])
        SKPaymentQueue.default().add(payment)
    }

    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction])
    {
        for transaction in transactions {
            switch transaction.transactionState {
            case .purchased,
                 .restored:
                print("Transaction Approved")
                print("Product Identifier: \(transaction.payment.productIdentifier)")
                self.deliverProduct(transaction)
                SKPaymentQueue.default().finishTransaction(transaction)

            case .failed:
                print("Transaction Failed")
                SKPaymentQueue.default().finishTransaction(transaction)

            case .deferred,
                 .purchasing:
                break
            }
        }
    }

    func deliverProduct(_ transaction:SKPaymentTransaction)
    {
    }

    func restorePurchases(_ sender: UIButton)
    {
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().restoreCompletedTransactions()
    }

    func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue)
    {
        print("Transactions Restored")

        for transaction in queue.transactions {
            processTransaction(transaction: transaction)
        }

        let alert = UIAlertController(title: "Thank You",
                                      message: "Your purchase(s) were restored.",
                                      preferredStyle: .alert)
        present(alert, animated: true)
    }

    private func processTransaction(transaction: SKPaymentTransaction)
    {
        guard let product = Product(rawValue: transaction.payment.productIdentifier) else {
            print("Unknown product identifier: \(transaction.payment.productIdentifier)")
            return
        }
        switch product {
        case .test1:
            print("Consumable Product Purchased")
            // Unlock Feature

        case .test2:
            print("Non-Consumable Product Purchased")
            // Unlock Feature

        case .test3:
            print("Auto-Renewable Subscription Product Purchased")
            // Unlock Feature

        case .test4:
            print("Free Subscription Product Purchased")
            // Unlock Feature

        case .test5:
            print("Non-Renewing Subscription Product Purchased")
            // Unlock Feature
        }
    }

    // Screen Layout Methods

    func numberOfSections(in tableView: UITableView) -> Int
    {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        return self.productsArray.count + 1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        let cellFrame = CGRect(x: 0, y: 0, width: self.tableView.frame.width, height: 52.0)
        let retCell = UITableViewCell(frame: cellFrame)

        if self.productsArray.count != 0 {
            if indexPath.row == Product.allValues.count
            {
                let restoreButton = UIButton(frame: CGRect(x: 10.0, y: 10.0, width: UIScreen.main.bounds.width - 20.0, height: 44.0))
                restoreButton.titleLabel?.font = UIFont(name: "HelveticaNeue-Bold", size: 20)
                restoreButton.addTarget(self, action: #selector(ViewController.restorePurchases(_:)), for: .touchUpInside)
                restoreButton.backgroundColor = .black
                restoreButton.setTitle("Restore Purchases", for: .normal)
                retCell.addSubview(restoreButton)
            } else {
                let singleProduct = productsArray[indexPath.row]

                let titleLabel = UILabel(frame: CGRect(x: 10.0, y: 0.0, width: UIScreen.main.bounds.width - 20.0, height: 25.0))
                titleLabel.textColor = .black
                titleLabel.text = singleProduct.localizedTitle
                titleLabel.font = UIFont(name: "HelveticaNeue", size: 20)
                retCell.addSubview(titleLabel)

                let descriptionLabel = UILabel(frame: CGRect(x: 10.0, y: 10.0, width: UIScreen.main.bounds.width - 70.0, height: 40.0))
                descriptionLabel.textColor = .black
                descriptionLabel.text = singleProduct.localizedDescription
                descriptionLabel.font = UIFont(name: "HelveticaNeue", size: 12)
                retCell.addSubview(descriptionLabel)

                let buyButton = UIButton(frame: CGRect(x: UIScreen.main.bounds.width - 60.0, y: 5.0, width: 50.0, height: 20.0))
                buyButton.titleLabel?.font = UIFont(name: "HelveticaNeue", size: 12)
                buyButton.tag = indexPath.row
                buyButton.addTarget(self, action: #selector(ViewController.buyProduct(_:)), for: .touchUpInside)
                buyButton.backgroundColor = .black
                let numberFormatter = NumberFormatter()
                numberFormatter.numberStyle = .currency
                numberFormatter.locale = .current
                buyButton.setTitle(numberFormatter.string(from: singleProduct.price), for: .normal)
                retCell.addSubview(buyButton)
            }
        }

        return retCell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
    {
        return 52.0
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    {
        tableView.deselectRow(at: indexPath, animated: true)
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
    {
        if section == 0 {
            return 64.0
        }

        return 32.0
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
    {
        let ret = UILabel(frame: CGRect(x: 10, y: 0, width: self.tableView.frame.width - 20, height: 32.0))
        ret.backgroundColor = .clear
        ret.text = "In-App Purchases"
        ret.textAlignment = .center
        return ret
    }
}

Upvotes: 2

Related Questions