Reputation: 967
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()
}
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
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