Removing Ads with purchase and restore - Swift - ios

Working in xcode 6 and swift, I am trying to get the in-app purchases to remove ads working. The purchase seems to work, but the restore not so much. I kinda got it to work but it does not update the controllers.
In every viewcontroller I have the following to show ads and check if already purchased to hide:
I have an #IBOutlet for the banner:
#IBOutlet weak var topAdBanner: ADBannerView!
As well as the following code:
override func viewDidLoad() {
super.viewDidLoad()
if NSUserDefaults.standardUserDefaults().boolForKey("purchased") ==
true || purchased == true {
self.topAdBanner.hidden = true
} else {
var topAdBanner: ADBannerView
self.topAdBanner.hidden = true
self.topAdBanner.delegate = self
}
Then on my menuviewcontroler this is the code I have which is where my problem is. The app is localized into a few languages, hence the extra localization code:
import UIKit
import StoreKit
class MenuViewController: UIViewController, SKProductsRequestDelegate,
SKPaymentTransactionObserver {
var product_id: NSString?;
#IBOutlet weak var descriptionText: UILabel!
#IBOutlet weak var purchaseButton: UIButton!
#IBOutlet weak var purchaseLabel: UILabel!
#IBOutlet weak var restoreButton: UIButton!
#IBOutlet weak var restoreLabel: UILabel!
#IBOutlet weak var cancelButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
product_id = "app.removeads";
self.descriptionText.text =
NSLocalizedString("REMOVE_ADS_DESCRIPTION", comment:
"REMOVE_ADS_DESCRIPTION")
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
}
func buyNonConsumable(){
if (SKPaymentQueue.canMakePayments())
{
var productID:NSSet = NSSet(object: self.product_id!);
var productsRequest:SKProductsRequest = SKProductsRequest
(productIdentifiers: productID as Set<NSObject>);
productsRequest.delegate = self;
productsRequest.start();
self.descriptionText.text = NSLocalizedString(
"FIRST_PROCESSING", comment: "first_processing")
}else{
self.descriptionText.text = NSLocalizedString("PAYMENT_DISABELD",
comment: "PAYMENT_DISABELD")
}
}
// Helper Methods
func buyProduct(product: SKProduct){
self.descriptionText.text = NSLocalizedString("SECOND_PROCESSING",
comment: "second_processing")
var payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
// Delegate Methods for IAP
func productsRequest (request: SKProductsRequest, didReceiveResponse
response: SKProductsResponse) {
self.descriptionText.text = NSLocalizedString("THIRD_PROCESSING",
comment: "third_processing")
var count : Int = response.products.count
if (count>0) {
var validProducts = response.products
var validProduct: SKProduct = response.products[0] as! SKProduct
if (validProduct.productIdentifier == self.product_id) {
println(validProduct.localizedTitle)
println(validProduct.localizedDescription)
println(validProduct.price)
buyProduct(validProduct);
} else {
println(validProduct.productIdentifier + "Fehler")
}
} else {
}
}
#IBAction func purchaseButton(sender: AnyObject) {
buyNonConsumable()
}
#IBAction func restoreButton(sender: AnyObject) {
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
self.descriptionText.text = NSLocalizedString("FIRST_PROCESSING",
comment: "first_processing")
}
func paymentQueueRestoreCompletedTransactionsFinished(queue:
SKPaymentQueue!) {
println("Transactions Being Restored")
var purchasedItemIDS = []
for transaction:SKPaymentTransaction in queue.transactions as!
[SKPaymentTransaction] {
if transaction.payment.productIdentifier == self.product_id
{
println("Product Already Purchased")
// Unlock Feature
self.descriptionText.text =
NSLocalizedString("UNLOCKED_LABEL", comment: "unlocked")
purchased = true
NSUserDefaults.standardUserDefaults().setBool(true, forKey:
"purchased")
NSUserDefaults.standardUserDefaults().synchronize()
SKPaymentQueue.defaultQueue().finishTransaction(
transaction)
}
}
var alert = UIAlertView(title: "Thank You", message: "Your purchase
was restored. Go back to main screen", delegate: nil,
cancelButtonTitle: "OK")
alert.show()
}
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions
transactions: [AnyObject]!) {
for transaction in transactions as! [SKPaymentTransaction] {
switch transaction.transactionState {
case SKPaymentTransactionState.Purchased:
if (transaction.downloads != nil) {
SKPaymentQueue.defaultQueue().startDownloads(
transaction.downloads)
} else {
// Unlock feature or content here before
// finishing transaction
self.descriptionText.text =
NSLocalizedString("UNLOCKED_LABEL", comment:
"unlocked")
purchased = true
NSUserDefaults.standardUserDefaults().setBool(true,
forKey: "purchased")
NSUserDefaults.standardUserDefaults().synchronize()
SKPaymentQueue.defaultQueue().finishTransaction(
transaction)
}
case SKPaymentTransactionState.Restored:
println("Restored")
self.descriptionText.text =
NSLocalizedString("RESTORED_LABEL", comment:
"restored")
purchased = true
NSUserDefaults.standardUserDefaults().setBool(true,
forKey:"purchased")
NSUserDefaults.standardUserDefaults().synchronize()
break
case SKPaymentTransactionState.Failed:
self.descriptionText.text = NSLocalizedString(
"ERROR_HEADER",comment: "error")
SKPaymentQueue.defaultQueue().finishTransaction(
transaction)
default:
break
}
}
}
}
Purchasing the app works. Tested is out and it removes the ads.
Restoring however finds the product and says successfully restored but the adds do not go away.
Another problem i seem to have is that when I load the page that has the option to purchase and restore, it asks you for your itunes app store log in as soon as the page loads without pressing anything. I am not sure if it is related to my code.
I have spend the whole day trying to solve this and looking through the forums and posts but I cannot seem to solve it. Any help will be greatly appreciated.

Regarding your last question - yes, asking the iTunes password is related to your code, because the password dialog could appear when you do addTransactionObserver having transactions in the queue. You do it first time (don't do it multiple times though) in viewDidLoad. Better add the observer only when you're doing some action related to purchases - actually purchasing or restoring purchases.
viewDidLoad on your controllers is executed only the time they're added to the view hierarchy. Thus if they were added to the view hierarchy before your purchase/restore controller then your check wouldn't execute. Either move your checks to viewDidAppear, or do some notification to refresh UI state via NSNotificationCenter.

Related

SKProductsRequest returning 0 Products

I'm trying to do IAP however for some reason, my SKProductsRequest returns 0 products.
-Test products have been added to iTunes connect properly
-Banking and Taxing information is filled
-The product's bundle id matches the app bundle id
-I've waited up to two days for it to get processed through the servers
I used this youtube tutorial to build the app:
https://www.youtube.com/watch?v=zRrs7O5yjKI
And here is the code:
import UIKit
import StoreKit
class ViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
#IBOutlet weak var lblAd: UILabel!
#IBOutlet weak var lblCoinAmount: UILabel!
#IBOutlet weak var outRemoveAds: UIButton!
#IBOutlet weak var outAddCoins: UIButton!
#IBOutlet weak var outRestorePurchases: UIButton!
var coins = 50
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
outRemoveAds.isEnabled = false
outAddCoins.isEnabled = false
outRestorePurchases.isEnabled = false
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID: NSSet = NSSet(objects: "com.IAPTesters.10Dolla", "com.IAPTesters.RemoveAds")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
print("please enable IAPS")
}
}
#IBAction func btnRemoveAds(_ sender: Any) {
print("rem ads")
for product in list {
let prodID = product.productIdentifier
if(prodID == "com.IAPTesters.RemoveAds") {
p = product
buyProduct()
}
}
}
#IBAction func btnAddCoins(_ sender: Any) {
for product in list {
let prodID = product.productIdentifier
if(prodID == "com.IAPTesters.10Dolla") {
p = product
buyProduct()
}
}
}
#IBAction func btnRestorePurchases(_ sender: Any) {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
func buyProduct() {
print("buy " + p.productIdentifier)
let pay = SKPayment(product: p)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(pay as SKPayment)
}
func removeAds() {
lblAd.removeFromSuperview()
}
func addCoins() {
coins += 50
lblCoinAmount.text = "\(coins)"
}
var list = [SKProduct]()
var p = SKProduct()
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("product request")
let myProduct = response.products
for product in myProduct {
print("product added")
print(product.productIdentifier)
print(product.localizedTitle)
print(product.localizedDescription)
print(product.price)
list.append(product)
}
outRemoveAds.isEnabled = true
outAddCoins.isEnabled = true
outRestorePurchases.isEnabled = true
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
print("transactions restored")
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "com.IAPTesters.RemoveAds":
print("remove ads")
removeAds()
case "com.IAPTesters.10Dolla":
print("add coins to account")
addCoins()
default:
print("IAP not found")
}
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("add payment")
for transaction: AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
print(trans.error)
switch trans.transactionState {
case .purchased:
print("buy ok, unlock IAP HERE")
print(p.productIdentifier)
let prodID = p.productIdentifier
switch prodID {
case "com.IAPTesters.RemoveAds":
print("remove ads")
removeAds()
case "com.IAPTesters.10Dolla":
print("add coins to account")
addCoins()
default:
print("IAP not found")
}
queue.finishTransaction(trans)
case .failed:
print("buy error")
queue.finishTransaction(trans)
break
default:
print("Default")
break
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
When you run the app it will print "IAP is enabled, loading" and than "product request" but nothing else.
If I print response.invalidProductIdentifiers in the productsRequest function it will return my products: ["com.IAPTesters.RemoveAds", "com.IAPTesters.10Dolla"]
Thanks in advance for the help
It turns out that my banking and taxing information was filled out incorrectly. I refilled it and than I had to wait about 30 minutes for it to work again. Everything is now working correctly! Thanks for all the help
If you refer to the SKProductsRequest documentation you will see:
Note
Be sure to keep a strong reference to the request object; otherwise, the system might deallocate the request before it can complete.
Your SKProductsRequest instance is a local constant in viewDidLoad. This will be deallocated as soon as viewDidLoad exits and since the product request will complete asynchronously, this will be before the request is completed and you will therefore never get a call back.
You should retain your SKProductsRequest in a property so that it isn't released.
class ViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
#IBOutlet weak var lblAd: UILabel!
#IBOutlet weak var lblCoinAmount: UILabel!
#IBOutlet weak var outRemoveAds: UIButton!
#IBOutlet weak var outAddCoins: UIButton!
#IBOutlet weak var outRestorePurchases: UIButton!
var productsRequest: SKProductsRequest?
var coins = 50
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
outRemoveAds.isEnabled = false
outAddCoins.isEnabled = false
outRestorePurchases.isEnabled = false
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
let productID: NSSet = NSSet(objects: "com.IAPTesters.10Dolla", "com.IAPTesters.RemoveAds")
self.productsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request?.delegate = self
request?.start()
} else {
print("please enable IAPS")
}
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("product request")
let myProduct = response.products
for product in myProduct {
print("product added")
print(product.productIdentifier)
print(product.localizedTitle)
print(product.localizedDescription)
print(product.price)
list.append(product)
}
outRemoveAds.isEnabled = true
outAddCoins.isEnabled = true
outRestorePurchases.isEnabled = true
self.productsRequest = nil
}
FYI, your implementation of paymentQueueRestoreCompletedTransactionsFinished is incorrect; you should process the restored transactions in updatedTransactions through the .restored transaction state. The paymentQueueRestoreCompletedTransactionsFinished method should only be used to update your UI or perform any other tasks that are required when the restoration process is complete.
If I 'm understood correctly then the problem with your implementation is you have created your products in iTunes with wrong identifiers. As I know the product identifiers should be like your application bundle identifier followed by your product functionality i.e, COM.COMPANYNAME.MYNEWAPPLICATION.REMOVEADS, COM.COMPANYNAME.MYNEWAPPLICATION.10DOLLA
Sample code be like,
typealias SSIAPHelperCompletion = (_ result: [SKProduct]?, _ error: Error?) ->Void
class SSIAPHelper: NSObject {
fileprivate let productsRequest = SKProductsRequest(productIdentifiers: Set(arrayLiteral: "com.companyname.appname.removeads","com.companyname.appname.10dolla"))
fileprivate var completion: SSIAPHelperCompletion?
static let shared = SSIAPHelper()
func requestProducts(completionHandler: #escaping SSIAPHelperCompletion) {
self.completion = completionHandler
self.productsRequest.delegate = SSIAPHelper.shared
self.productsRequest.start()
}
}
extension SSIAPHelper: SKProductsRequestDelegate{
func requestDidFinish(_ request: SKRequest) {
print(#function)
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print(#function, response.products)
if let lCompletion = self.completion{
lCompletion(response.products, nil)
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print(#function)
}
}
You can get products by simply calling below function from your viewDidLoad()
SSIAPHelper.shared.requestProducts(completionHandler: { (result, error) in
})

Where should I declare/instantiate my Store object to keep it available while app running?

I have an iOS app written in Swift where I have written code for the store in it's own class (Store). I then have a view controller (StoreController) which displays the product details and presents buttons so that the user can buy/restore purchases. I am using the delegate pattern.
My question is where should I create the Store object? Currently I'm declaring it as a property of StoreController which I think is wrong. I would prefer to be able to create it when my app loads and keep it until it quits.
The current problem is that if I test it and navigate to the StoreController view (initiating the request via Store) then press 'back' the app freezes. I assume because it's waiting on the response?
I saw one example online where someone created it in the AppDelegate file but this didn't work for me as I wasn't able to reference it from my StoreController.
This is my Store class:
import Foundation
import StoreKit
protocol ClassStoreDelegate: class {
func storeUpdateReceived(store: Store)
}
class Store: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
// Properties
weak var delegate: ClassStoreDelegate?
var list = [SKProduct]()
var p = SKProduct()
var defaults = UserDefaults.standard // Where we save whether the user is pro
var localTitle: String?
var localDescription: String?
var localPrice: String?
// Methods
// Calling the delegate method
func storeUpdate() {
delegate?.storeUpdateReceived(store: self)
}
// Buy the product
func buy() {
for product in list {
let prodID = product.productIdentifier
if(prodID == "com.squidgylabs.pro") {
p = product
buyProduct()
}
}
}
// Restore products
func restore() {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
func getProducts() {
if(SKPaymentQueue.canMakePayments()) {
let productID: NSSet = NSSet(objects: "com.squidgylabs.pro")
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
} else {
delegate?.storeUpdateReceived(store: self)
}
}
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let myProduct = response.products
for product in myProduct {
list.append(product)
}
// Update labels
localTitle = list[0].localizedTitle
localDescription = list[0].localizedDescription
// Format the price and display
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .currency
if let formattedPrice = formatter.string(from: list[0].price){
localPrice = ("buy \(formattedPrice)")
delegate?.storeUpdateReceived(store: self)
}
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
let transactionsArray = queue.transactions
if (transactionsArray.isEmpty) {
delegate?.storeUpdateReceived(store: self)
}
else {
for transaction in transactionsArray {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "com.squidgylabs.pro":
defaults.set(true, forKey: "pro")
delegate?.storeUpdateReceived(store: self)
default:
delegate?.storeUpdateReceived(store: self)
}
}
}
}
func buyProduct() {
let pay = SKPayment(product: p)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(pay as SKPayment)
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction: AnyObject in transactions {
let trans = transaction as! SKPaymentTransaction
switch trans.transactionState {
case .purchased:
let prodID = p.productIdentifier
switch prodID {
case "com.squidgylabs.pro":
defaults.set(true, forKey: "pro")
delegate?.storeUpdateReceived(store: self)
default:
delegate?.storeUpdateReceived(store: self)
}
queue.finishTransaction(trans)
break
case .failed:
delegate?.storeUpdateReceived(store: self)
queue.finishTransaction(trans)
break
case .restored:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
queue.finishTransaction(trans)
break
default:
break
}
}
}
}
And this is my StoreController (view controller):
import UIKit
class StoreController: UIViewController, ClassStoreDelegate {
// Properties
let store = Store()
// Outlets
#IBOutlet weak var localTitle: UILabel!
#IBOutlet weak var localDescription: UILabel!
#IBOutlet weak var buy: UIButton!
#IBOutlet weak var restore: UIButton!
// Actions
#IBAction func buy(_ sender: UIButton) {
print("buy pressed")
store.buy()
}
#IBAction func restore(_ sender: UIButton) {
print("restore pressed")
store.restore()
}
// Methods
override func viewDidLoad() {
super.viewDidLoad()
store.delegate = self // bind the delegate like this?
// Get list of products for the store
localTitle.isEnabled = false
localDescription.isEnabled = false
buy.isEnabled = false
restore.isEnabled = false
self.navigationItem.title = "Store"
// update once the list of products is got from the store object
store.getProducts()
}
// Running the delegate update
func storeUpdateReceived(store: Store) {
print("storeUpdateReceived activated")
if ((store.localTitle) != nil) {
localTitle.text = store.localTitle!
localDescription.text = store.localDescription
buy.setTitle(store.localPrice, for: .normal)
localTitle.isEnabled = true
localDescription.isEnabled = true
buy.isEnabled = true
restore.isEnabled = true
}
}
}
You're right - instantiate it in your AppDelegate. You know it will be called once and only once, so it's a good place to initialise things.
You can access the AppDelegate from anywhere in your app
appDelegate = (UIApplication.shared.delegate as! AppDelegate)

App crashes with in-app purchase error

I use this code to create my in-app purchase:
import UIKit
import StoreKit
class ViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver, SKStoreProductViewControllerDelegate {
var product_id: String?
#IBOutlet weak var buyButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
product_id = "product_id"
SKPaymentQueue.default().add(self)
}
#IBAction func unlockAction(sender: AnyObject) {
print("About to fetch the product...")
// Can make payments
if (SKPaymentQueue.canMakePayments())
{
let productID:NSSet = NSSet(object: self.product_id!);
let productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>);
productsRequest.delegate = self;
productsRequest.start();
print("Fetching Products");
}else{
print("Can't make purchases");
}
}
func buyProduct(product: SKProduct){
print("Sending the Payment Request to Apple");
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment);
}
func productsRequest (_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let count : Int = response.products.count
if (count>0) {
let validProduct: SKProduct = response.products[0] as SKProduct
if (validProduct.productIdentifier == self.product_id) {
buyProduct(product: validProduct);
} else {
print(validProduct.productIdentifier)
}
} else {
print("nothing")
}
}
But if I click on button(unlockAction) twice or click once, move on different controller, return and click once more my app crashes on this line:
SKPaymentQueue.default().add(payment);
with error (lldb)
How to fix it?
You have to use viewWillDissappear() function for that in your billing controller. Try that one .
override func viewWillDisappear(_ animated: Bool) {
SKPaymentQueue.default().remove(self)
}
I was also getting that problem and that has been resolve my problem.
The error was with SKPaymentQueue.default().add(payment) line in the code. So try this solution and be reliable productive programmer.
Happy Coding

Why is my array out of bounds?

I am trying to incorporate an in-app purchase. For some reason I am getting a fatal error that states 'fatal error: Index out of range' with an array that holds the SKProduct products. I have one nonconsumable in app purchase, but may decide to add more later. I am trying to access the PREMIUM_PRODUCT_ID product so I can make a purchase. The error occurs when the purchaseMyProduct function is called. Any ideas in why the iapProducts array is out of bounds? Thanks for your help!
The fatal error occurs on this line purchaseMyProduct(product: iapProducts[0])
import UIKit
import StoreKit
class Settings: UIViewController, SKProductsRequestDelegate,
SKPaymentTransactionObserver {
let PREMIUM_PRODUCT_ID = "---------------"
var productID = ""
var productsRequest = SKProductsRequest()
var iapProducts = [SKProduct]()
var nonConsumablePurchaseMade = UserDefaults.standard.bool(forKey: "nonConsumablePurchaseMade")
#IBOutlet weak var adsBtn: UIButton!
#IBAction func restorePurchase(_ sender: Any) {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
// Check your In-App Purchases
print("NON CONSUMABLE PURCHASE MADE: \(nonConsumablePurchaseMade)")
// Fetch IAP Products available
fetchAvailableProducts()
UIAlertView(title: "IAP Tutorial",
message: "You've successfully restored your purchase!",
delegate: nil, cancelButtonTitle: "OK").show()
}
#IBAction func review(_ sender: Any) {
UIApplication.shared.openURL(NSURL(string: "-----------------")! as URL)
}
#IBAction func removeAds(_ sender: Any) {
//UIApplication.shared.openURL(NSURL(string: "----------------")! as URL)
purchaseMyProduct(product: iapProducts[0])
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
...
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
nonConsumablePurchaseMade = true
UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade")
/*UIAlertView(title: "IAP Tutorial",
message: "You've successfully restored your purchase!",
delegate: nil, cancelButtonTitle: "OK").show()*/
}
func fetchAvailableProducts() {
// Put here your IAP Products ID's
let productIdentifiers = NSSet(objects:
PREMIUM_PRODUCT_ID
)
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
productsRequest.delegate = self
productsRequest.start()
}
func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) {
if (response.products.count > 0) {
iapProducts = response.products
// 1st IAP Product (Consumable) ------------------------------------
let firstProduct = response.products[0] as SKProduct
// Get its price from iTunes Connect
let numberFormatter = NumberFormatter()
numberFormatter.formatterBehavior = .behavior10_4
numberFormatter.numberStyle = .currency
numberFormatter.locale = firstProduct.priceLocale
let price1Str = numberFormatter.string(from: firstProduct.price)
// Show its description
//consumableLabel.text = firstProduct.localizedDescription + "\nfor just \(price1Str!)"
// ------------------------------------------------
// 2nd IAP Product (Non-Consumable) ------------------------------
let secondProd = response.products[0] as SKProduct
// Get its price from iTunes Connect
numberFormatter.locale = secondProd.priceLocale
let price2Str = numberFormatter.string(from: secondProd.price)
// Show its description
//nonConsumableLabel.text = secondProd.localizedDescription + "\nfor just \(price2Str!)"
// ------------------------------------
}
}
func canMakePurchases() -> Bool { return SKPaymentQueue.canMakePayments() }
func purchaseMyProduct(product: SKProduct) {
if self.canMakePurchases() {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
print("PRODUCT TO PURCHASE: \(product.productIdentifier)")
productID = product.productIdentifier
// IAP Purchases dsabled on the Device
} else {
UIAlertView(title: "IAP Tutorial",
message: "Purchases are disabled in your device!",
delegate: nil, cancelButtonTitle: "OK").show()
}
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
if let trans = transaction as? SKPaymentTransaction {
switch trans.transactionState {
case .purchased:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
// The Consumable product (10 coins) has been purchased -> gain 10 extra coins!
if productID == PREMIUM_PRODUCT_ID {
// Save your purchase locally (needed only for Non-Consumable IAP)
nonConsumablePurchaseMade = true
UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade")
//premiumLabel.text = "Premium version PURCHASED!"
UIAlertView(title: "IAP Tutorial",
message: "You've successfully unlocked the Premium version!",
delegate: nil,
cancelButtonTitle: "OK").show()
}
break
case .failed:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
break
case .restored:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
break
default: break
}}}
}
}
Your code is way too long, so I took the liberty of making an educated guess what is relevant:
import UIKit
import StoreKit
class Settings: UIViewController, SKProductsRequestDelegate,
SKPaymentTransactionObserver {
var iapProducts = [SKProduct]()
#IBAction func removeAds(_ sender: Any) {
purchaseMyProduct(product: iapProducts[0])
}
}
When you declare iapProducts, you are creating an empty array that contains no values.
Then, in your removeAds function, you are telling it to do something with the first element.
I am going to take a wild guess that nothing exists, so you are forcing it to access an object that doesn't exist.
Try the following and see if it fixes your issue.
var iapProducts: [SKProduct]?
#IBAction func removeAds(_ sender: Any) {
guard let product = iapProducts?.first else { return }
purchaseMyProduct(product: product)
}
This accomplishes two things:
It makes your [SKProduct] array an optional
The guard statement safely unwraps the first product, so only if one exists, will it try to call your purchaseMyProduct function. Otherwise, it just exits

In-App Purchase : Remove Ad Banners & Interstitial Ads - Code Assistance

I have a simple single view application project - a one (1) page app that displays some random text to the user.
I have successfully incorporated Ad Banners and Interstitial Ads.
I have set up another view controller (InAppViewController.swift) to handle a pop-up page that allows the user to make an in-app purchase to remove all ads (AdBanners & InterstitialAds).
In my second view controller (InAppViewController.swift), I have the following code:
AMENDED CODE:
// InAppPViewController.swift
import UIKit
import StoreKit
import iAd
class InAppPViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
let defaults = NSUserDefaults.standardUserDefaults()
var product_id: NSString?;
#IBOutlet weak var unlockAction: UIButton!
#IBOutlet var adBannerView: ADBannerView?
override func viewDidLoad() {
product_id = "holymoly.iap.removeads";
super.viewDidLoad()
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
//Check if product is purchased
if (defaults.boolForKey("purchased")){
self.adBannerView?.hidden = true
}
else if (!defaults.boolForKey("stonerPurchased")){
print("false")
self.adBannerView?.hidden = false
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func unlockAction(sender: AnyObject) {
print("About to fetch the products");
// We check that we are allow to make the purchase.
if (SKPaymentQueue.canMakePayments())
{
let productID:NSSet = NSSet(object: self.product_id!);
let productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>);
productsRequest.delegate = self;
productsRequest.start();
print("Fething Products");
}else{
print("can't make purchases");
}
}
func buyProduct(product: SKProduct){
print("Sending the Payment Request to Apple");
let payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment);
}
//Delegate Methods for IAP
func productsRequest (request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
let count : Int = response.products.count
if (count>0) {
let validProduct: SKProduct = response.products[0] as SKProduct
if (validProduct.productIdentifier == self.product_id) {
print(validProduct.localizedTitle)
print(validProduct.localizedDescription)
print(validProduct.price)
buyProduct(validProduct);
} else {
print(validProduct.productIdentifier)
}
} else {
print("nothing")
}
}
func request(request: SKRequest, didFailWithError error: NSError) {
print("Error Fetching product information");
}
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("Received Payment Transaction Response from Apple");
for transaction:AnyObject in transactions {
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction{
switch trans.transactionState {
case .Purchased:
print("Product Purchased");
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
defaults.setBool(true , forKey: "purchased")
self.adBannerView?.hidden = true
break;
case .Failed:
print("Purchased Failed");
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
self.adBannerView?.hidden = false
break;
case .Restored:
print("Already Purchased");
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
default:
self.adBannerView?.hidden = true
break;
}
}
}
}
}
And in my 'original' view controller (ViewController.swift) I have added this code:
// ViewController.swift
import UIKit
import MessageUI
import Social
import iAd
class ViewController: UIViewController, MFMailComposeViewControllerDelegate, MFMessageComposeViewControllerDelegate, ADBannerViewDelegate, ADInterstitialAdDelegate
{
var interstitialAd:ADInterstitialAd!
var interstitialAdView: UIView = UIView()
#IBOutlet var adBannerView: ADBannerView?
#IBAction func someFunkyButton(sender: AnyObject) {
//Interstitial Ad:
let rand = Int(arc4random_uniform(4))
print(rand)
let adNo = 2
if(adNo == rand)
{
loadInterstitialAd()
}
}
let defaults = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
//Check if product is purchased
if (defaults.boolForKey("purchased")){
// Advertising Banner:
self.canDisplayBannerAds = true
self.adBannerView?.delegate = self
self.adBannerView?.hidden = true
}
else if (!defaults.boolForKey("stonerPurchased")){
print("false")
// Advertising Banner:
self.canDisplayBannerAds = true
self.adBannerView?.delegate = self
self.adBannerView?.hidden = false
}
The code shows as error-free.
It runs on my actual iPhone (simulator) and the in-app purchases work.
But the ad banners still show. I'm trying to show the ad banners with:
(i) In ViewController.swift:
self.canDisplayBannerAds = true
self.adBannerView?.delegate = self
self.adBannerView?.hidden = false
(ii) In InAppViewController.swift:
self.adBannerView?.hidden = false
and not show the ad banners with:
(i) In ViewController.swift:
self.canDisplayBannerAds = true
self.adBannerView?.delegate = self
self.adBannerView?.hidden = true
(ii) In InAppViewController.swift:
self.adBannerView?.hidden = true
but it's obviously not working.
Questions:
How can I amend my code and stop these ad banners from showing?
How can I also stop my interstitial ads from showing?
I feel I'm close, very close ... but that cigar is yet to land!
If you're implementing your own ADBannerView then you need to remove self.canDisplayBannerAds = true.
self.canDisplayBannerAds = true can be used for a no hassle way of implementing iAd banners in your application. This will create an ADBannerView for you and show or hide the ADBannerView on the bottom of your view depending on whether it receives an ad or not from the iAd network.
You either implement your own ADBannerView or use self.canDisplayBannerAds = true, not both.
First of all removeAds is inside btnRemoveAds so move it outside. Secondly, one approach would be to save the product identifiers of the purchased products in UserDefaults so that on quit and launch of the app the ads will not appear since the user has purchased the inapp. So what you will do on viewDidLoad is check if the product identifier is purchased (from NSUserDefaults, if it was purchased earlier it will be set), if it is purchased disable ads from the beginning else show ads. And when item is purchased then set the state in NSUserDefaults and update UI accordingly.

Resources