StoreKit:从 Swift 1.2 转换为 Swift 3 时出错

StoreKit: Error in converting from Swift 1.2 to Swift 3

我得到了一个示例项目来学习如何使用 Apple 的 StoreKit,这样我就可以学习将自动续订订阅服务应用到我的应用程序和任何应用程序内服务。

问题是示例项目来自 Swift 1.2 并将其转换为 Swift 3,我遇到了几个错误,但我遇到了 2 个警告和 2 个错误。希望有人能帮助我。

此外,在将代码转换为 Swift 3 时,代码仍然有效吗?既然老了?应用内购买是否有任何重大变化?

带有警告和错误的代码

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()
}

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()
}

表格视图

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
}

第一个函数中的两个问题都来自 before Swift 3,NSArrays 是在没有泛型类型的情况下导入的(即 [Any],而不是 [SKProduct])。

只需删除 as? SKProduct 部分即可修复警告,但在一次调用中添加所有内容会更清晰:

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

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

错误是因为虽然 response.productsresponse.invalidProductIdentifiers 最初是作为 [Any] 导入的,但现在输入了它们([SKProduct][String])。最简单的解决方案就是直接使用数组:

// Old:
products = response.invalidProductIdentifiers

for product in products

// New:
for product in response.invalidProductIdentifiers

因为它只是打印,我可能直接打印数组。

第二个函数的错误是因为编译器需要知道变量应该是什么类型的数组。从名称来看,我猜它应该是一个 [String],但它没有被使用(正如同一行上的警告所指示的),所以你也可以删除该行。


完整的 updated/modernized/uniform-styled 视图控制器:

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 { [=12=].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
    }
}