I successfully implemented an in-app purchase to remove ads in my app. It worked the first time I tried it but after I ran the app on my phone a few more times it started to just show a white ad banner instead of hidding the ad banner like it used to.
Here is the code for the StartScreen.m of my app as well as the PurchaseViewController.m to buy the IAP to remove ads. I also got a Warning saying I am using 10 instances of ADBanner even though I have them removed whenever the view disappers. Thank you for any and all help.
//
//StartScreen.m
//
#interface StartScreen ()
{
BOOL _bannerIsVisible;
}
#end
#implementation StartScreen
- (void)viewDidLoad {
//Ads
self.adBanner.delegate = self;
}
- (void)viewWillDisappear:(BOOL)animated
{
[self.adBanner removeFromSuperview];
self.adBanner.delegate = nil;
self.adBanner = nil;
}
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
// Check for Remove Ads IAP
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if ([prefs boolForKey:#"RemoveAds"] == TRUE) {
self.adBanner.hidden = YES;
_bannerIsVisible = NO;
} else if (!_bannerIsVisible)
{
self.adBanner.hidden = NO;
_bannerIsVisible = YES;
}
}
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
NSLog(#"Failed to retreive ad");
// Check for Remove Ads IAP
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if ([prefs boolForKey:#"RemoveAds"] == TRUE) {
self.adBanner.hidden = YES;
_bannerIsVisible = NO;
}
}
//
// PurcahseViewController.m
//
#import "PurcahseViewController.h"
#implementation PurcahseViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.productID = #"com.app.iap1";
[self getProductID:self];
self.buyButton.enabled = NO;
NSLog(#"%#", self.productID);
}
- (void)getProductID:(UIViewController *)viewController {
if ([SKPaymentQueue canMakePayments]) {
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:self.productID]];
request.delegate = self;
[request start];
} else {
self.productDescription.text = #"Please enable in app purchase in your settings";
}
}
- (IBAction)purchaseItem:(id)sender {
SKPayment *payment = [SKPayment paymentWithProduct:self.product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (IBAction)restoreButton:(id)sender {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
[self UnlockPurchase];
}
- (void)Purchased {
NSLog(#"Item has been purchased");
}
#pragma mark _
#pragma mark SKProductsRequestDelegate
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *products = response.products;
if (products.count != 0) {
self.product = products[0];
self.buyButton.enabled = YES;
self.productTitle.text = self.product.localizedTitle;
self.productDescription.text = self.product.localizedDescription;
} else {
self.productTitle.text = #"404: Product Not Found";
}
products = response.invalidProductIdentifiers;
for (SKProduct *product in products) {
NSLog(#"404: Product Not Found: %#", product);
}
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased: [self UnlockPurchase];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:NSLog(#"Transaction Failed");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
default:
break;
}
}
}
-(void)UnlockPurchase {
self.buyButton.enabled = NO;
[self.buyButton setTitle:#"Purchased" forState:UIControlStateDisabled];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setBool:TRUE forKey:#"RemoveAds"];
[prefs synchronize];
[self Purchased];
}
First off, to answer your question, the reason your banner isn't hiding when your ad fails is because you're not hiding it. Whether or not [prefs boolForKey:#"RemoveAds"] == TRUE, you're going to want to hide that banner if you don't want the blank white bar in its place. To do so in the simplest possible way (without any animation), simplify your didFailToReceiveAdWithError: method, like so:
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
NSLog(#"Failed to retreive ad");
// Check for Remove Ads IAP
self.adBanner.hidden = YES;
_bannerIsVisible = NO;
}
And hide it in your viewDidLoad so it's hidden from the very beginning before any ad has loaded:
- (void)viewDidLoad {
//Ads
self.adBanner.delegate = self;
self.adBanner.hidden = YES;
}
That way, your ad will only unhide in bannerViewDidLoadAd: once an ad has successfully been loaded.
Secondly, your _bannerIsVisible boolean is unnecessary. Instead of using a separate boolean to check whether the banner is visible, you can just check whether it's hidden, with if (self.adBanner.hidden == YES) or if (self.adBanner.hidden == NO) or if (self.adBanner.hidden) or if (!self.adBanner.hidden). Eliminating that unnecessary boolean will cut down on the potential for error.
So, just to summarize, here's my suggestion for how your bannerViewDidLoadAd: and didFailToReceiveAdWithError: methods should look:
- (void)bannerViewDidLoadAd:(ADBannerView *)banner {
// Check for Remove Ads IAP
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if ([prefs boolForKey:#"RemoveAds"] == TRUE) {
self.adBanner.hidden = YES;
} else if (self.adBanner.hidden) {
self.adBanner.hidden = NO;
}
}
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
NSLog(#"Failed to retreive ad");
self.adBanner.hidden = YES;
}
Related
I've successfully implemented one consumable product, however i have no clue to to implement multiple consumable products. Id like to addd more ProductIdentifiers like com.lalala.20batteries, com.lalala.30batteries
can anyone please give me some guides
here is my code for the single consumable product
#interface ViewController () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
#end
#implementation ViewController
#define ProductIdentifier #"com.lalala.10batteries"
- (IBAction)taps10batteries{
NSLog(#"User requests to get 10 batteries");
if([SKPaymentQueue canMakePayments]){
NSLog(#"User can make payments");
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:ProductIdentifier]];
productsRequest.delegate = self;
[productsRequest start];
}
else{
NSLog(#"User cannot make payments due to parental controls");
}
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
SKProduct *validProduct = nil;
int count = [response.products count];
if(count > 0){
validProduct = [response.products objectAtIndex:0];
NSLog(#"Products Available!");
[self purchase:validProduct];
}
else if(!validProduct){
NSLog(#"No products available");
}
}
- (IBAction)purchase:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
NSLog(#"received restored transactions: %i", queue.transactions.count);
for(SKPaymentTransaction *transaction in queue.transactions){
if(transaction.transactionState == SKPaymentTransactionStateRestored){
//called when the user successfully restores a purchase
NSLog(#"Transaction state -> Restored");
[self get10Batteries];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for(SKPaymentTransaction *transaction in transactions){
switch(transaction.transactionState){
case SKPaymentTransactionStatePurchasing: NSLog(#"Transaction state -> Purchasing");
break;
case SKPaymentTransactionStatePurchased:
[self get10Batteries];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(#"Transaction state -> Purchased");
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Transaction state -> Restored");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
if(transaction.error.code == SKErrorPaymentCancelled){
NSLog(#"Transaction state -> Cancelled");
//the user cancelled the payment ;(
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)get10Batteries{
NSUbiquitousKeyValueStore *cloudstore1 = [NSUbiquitousKeyValueStore defaultStore];
//load cloud integer
coins = [cloudstore1 doubleForKey:#"yo" ];
coins = [[NSUserDefaults standardUserDefaults]
integerForKey:#"com.lalala.10batteries"];
coins += 10;
[[NSUserDefaults standardUserDefaults] setInteger:coins
forKey:#"com.lalala.10batteries"];
[[NSUserDefaults standardUserDefaults] synchronize];
_coinLabel.text = [NSString stringWithFormat:#"%li", (long)coins];
//save icloud
[cloudstore1 setDouble:coins forKey:#"yo"];
[cloudstore1 synchronize];
}
- (void)viewDidLoad {
[super viewDidLoad];
NSUbiquitousKeyValueStore *cloudstore1 = [NSUbiquitousKeyValueStore defaultStore];
_coinLabel.text = [cloudstore1 stringForKey:#"yo" ];
NSUserDefaults *coinsdefaults = [NSUserDefaults standardUserDefaults];
if([coinsdefaults objectForKey:#"com.lalala.10batteries"] != nil) {
coins = [[NSUserDefaults standardUserDefaults] integerForKey:#"com.lalala.10batteries"];
coins = [coinsdefaults integerForKey:#"com.lalala.10batteries"];
_coinLabel.text = [NSString stringWithFormat:#"%li", (long)coins];
}
}
Your code can easily support multiple products, just like it supports one...
First of all, declare your other product IDs just like you did with one:
#define ProductIdentifier1 #"com.lalala.10batteries"
#define ProductIdentifier2 #"com.lalala.20batteries"
Then, create a loadAllProducts method, which is exactly like the taps10batteries method, except you declare your SKProductsRequest with ALL of the product IDs, just like you did with one:
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObjects:ProductIdentifier1, ProductIdentifier2, nil]];
Unless you prefer loading each product individually, then you'll need to create a method for each product, i.e. taps20batteries and you can ignore the next part of my answer...
The callback method can return multiple products like it can return one. Handle the response as you see fit, just like you did with one:
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
int count = [response.products count];
if(count > 0){
_products = [response.products sortedArrayUsingComparator:^(id a, id b) { //sort all products by price
NSDecimalNumber *first = [(SKProduct*)a price];
NSDecimalNumber *second = [(SKProduct*)b price];
return [second compare:first];
}];
for (SKProduct *product in _products) {
NSLog(#"Product Available: %#", product.productIdentifier);
//do something with the product..
//perhaps gather the products into an array and present it somehow to the user so s/he can select what to buy?
}
}
else {
NSLog(#"No products available");
}
}
If you want a full implementation that you can use, try this one
see the line where you create SKProductsRequest object:
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:ProductIdentifier]];
NSSet is a bag with objects inside unsorted and only one kind of allowed inside the bag (no dupes).
All you have to do is change it like this:
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObjects:
#"com.blahblah.unlockall",
#"com.blahblah.sounds",
#"com.blahblah.alarms",
#"com.blahblah.wallpapers",
nil]];
then u should have a custom wrapper class (CustomIAPHelper) as in the tutorial.
in view controller let's say you have a tableView then the code follows like this:
- (void)reload {
_products = nil;
[self.tableView reloadData];
[CustomIAPHelper sharedInstance].delegate = self;
[[CustomIAPHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products) {
if (success) {
// Sort the products array (by price)
_products = [products sortedArrayUsingComparator:^(id a, id b) {
NSDecimalNumber *first = [(SKProduct*)a price];
NSDecimalNumber *second = [(SKProduct*)b price];
return [second compare:first];
}];
[self.tableView reloadData];
}
[refreshControl endRefreshing];
[activity stopAnimating];
[activity setHidden:YES];
}];
}
remember to setup and implement iAPHelper's protocol methods.
I have too many crash reports on system code. How can I find the issue on my application?
Crashed: com.apple.main-thread
EXC_BAD_ACCESS KERN_INVALID_ADDRESS at 0x81566238
libobjc.A.dylib objc_msgSend + 5 respondsToSelector:
StoreKit __34-[SKProductsRequest _handleReply:]_block_invoke + 446
libdispatch.dylib _dispatch_call_block_and_release + 10
UIKit UIApplicationMain + 1136
MyApp main.m line 13
UPDATED
The full code to process in-app purchases on the application:
#pragma mark In-App Purchase
- (NSString*)getProductId:(NSString*)feature {
NSBundle *bundle = [NSBundle mainBundle];
NSDictionary *info = [bundle infoDictionary];
NSString *bundleIdentifier = [info objectForKey: #"CFBundleIdentifier"];
return [NSString stringWithFormat:feature, [bundleIdentifier stringByReplacingOccurrencesOfString:#"-" withString:#""]];
}
- (void)requestItems:(NSString*)feature {
NSSet *productIdentifiers = [NSSet setWithObject:[self getProductId:feature]];
if ([feature isEqualToString:g100Items]) {
if (product100ItemsRequest) {
[product100ItemsRequest release];
product100ItemsRequest = nil;
}
product100ItemsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
product100ItemsRequest.delegate = self;
[product100ItemsRequest start];
// we will release the request object in the delegate callback
} else
if ([feature isEqualToString:gUnlimitedItems]) {
if (productUnlimitedItemsRequest) {
[productUnlimitedItemsRequest release];
productUnlimitedItemsRequest = nil;
}
productUnlimitedItemsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
productUnlimitedItemsRequest.delegate = self;
[productUnlimitedItemsRequest start];
// we will release the request object in the delegate callback
}
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
[products addObjectsFromArray:response.products];
for (SKProduct *product in response.products) {
if (product && [product.productIdentifier isEqualToString:[self getProductId:g100Items]]) {
[button100Items setTitle:[NSString stringWithFormat:g100ItemsButton, product.localizedPrice] forState:UIControlStateNormal];
// finally release the reqest we alloc/init’ed in requestItems
[product100ItemsRequest release];
product100ItemsRequest = nil;
}
if (product && [product.productIdentifier isEqualToString:[self getProductId:gUnlimitedItems]]) {
[buttonUnlimitedItems setTitle:[NSString stringWithFormat:gUnlimitedItemsButton, product.localizedPrice] forState:UIControlStateNormal];
// finally release the reqest we alloc/init’ed in requestItems
[productUnlimitedItemsRequest release];
productUnlimitedItemsRequest = nil;
}
}
for (NSString *invalidProductId in response.invalidProductIdentifiers) {
NSLog(#"Invalid product id: %#" , invalidProductId);
}
[[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerProductsFetchedNotification object:self userInfo:nil];
}
// call this method once on startup
- (void)loadStore:(BOOL)tryAgain {
if (tryAgain) {
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
NSMutableArray *oldProducts = products;
products = [[[NSMutableArray alloc] initWithObjects: nil] retain];
[oldProducts release];
}
// restarts any purchases if they were interrupted last time the app was open
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
// get the product description (defined in early sections)
[self requestItems:g100Items];
[self requestItems:gUnlimitedItems];
}
// call this before making a purchase
- (BOOL)canMakePurchases {
return [SKPaymentQueue canMakePayments];
}
// kick off the upgrade transaction
- (void)purchaseItems:(NSString*)feature {
bool ok = false;
for (SKProduct *product in products) {
if ([product.productIdentifier isEqualToString:[self getProductId:feature]]) {
SKPayment *payment = [SKPayment paymentWithProduct:product];
if (payment) {
[[SKPaymentQueue defaultQueue] addPayment:payment];
// Calling AppStore Dialog
}
ok = true;
break;
}
}
if (!ok) {
[self loadStore:YES];
[self showAlert:gInAppAlertTitle alertStr:gNoProductsToMakePurchase];
return;
}
}
// saves a record of the transaction by storing the receipt to disk
- (void)recordTransaction:(SKPaymentTransaction *)transaction {
if ([transaction.payment.productIdentifier isEqualToString:[self getProductId:g100Items]]) {
// save the transaction receipt to disk
[[NSUserDefaults standardUserDefaults] setValue:[NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]/*transaction.transactionReceipt*/ forKey:[self getProductId:g100Items]];
[[NSUserDefaults standardUserDefaults] synchronize];
} else
if ([transaction.payment.productIdentifier isEqualToString:[self getProductId:gUnlimitedItems]]) {
// save the transaction receipt to disk
[[NSUserDefaults standardUserDefaults] setValue:[NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]/*transaction.transactionReceipt*/ forKey:[self getProductId:gUnlimitedItems]];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
// enable pro features
- (bool)provideContent:(NSString *)productId {
if (productId) {
FirstViewController* firstViewController = [[tabBarController viewControllers] objectAtIndex:gSourceTabIndex];
if ([productId isEqualToString:[self getProductId:g100Items]]) {
firstViewController.itemCount += 100;
[firstViewController saveItems];
// 100 Items Provided
return true;
} else
if ([productId isEqualToString:[self getProductId:gUnlimitedItems]]) {
firstViewController.itemCount = gItemUnlimitedCount;
[firstViewController saveItems];
// Unlimited Items Provided
return true;
}
}
return false;
}
// removes the transaction from the queue and posts a notification with the transaction result
- (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful {
// remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:transaction, #"transaction" , nil];
if (wasSuccessful) {
// send out a notification that we’ve finished the transaction
[[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerTransactionSucceededNotification object:self userInfo:userInfo];
}
else {
// send out a notification for the failed transaction
[[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerTransactionFailedNotification object:self userInfo:userInfo];
}
}
// called when the transaction was successful
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
[self recordTransaction:transaction];
bool provided = [self provideContent:transaction.payment.productIdentifier];
[self finishTransaction:transaction wasSuccessful:YES];
}
// called when a transaction has been restored and successfully completed
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
[self recordTransaction:transaction.originalTransaction];
[self provideContent:transaction.originalTransaction.payment.productIdentifier];
[self finishTransaction:transaction wasSuccessful:YES];
}
// called when a transaction has failed
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
if (transaction.error.code != SKErrorPaymentCancelled) {
// error!
[self finishTransaction:transaction wasSuccessful:NO];
[self showAlert:gInAppAlertTitle alertStr:[transaction.error localizedDescription]];
} else {
// this is fine, the user just cancelled, so don’t notify
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
}
// called when the transaction status is updated
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
break;
default:
break;
}
}
}
- (IBAction)process100Items:(id)sender {
if ([self canMakePurchases]) {
[self purchaseItems:g100Items];
} else {
[self showAlert:gInAppAlertTitle alertStr:gCanNotMakePurchases];
}
// Buy 100 Items Button Pressed
}
- (IBAction)processUnlimitedItems:(id)sender {
if ([self canMakePurchases]) {
[self purchaseItems:gUnlimitedItems];
} else {
[self showAlert:gInAppAlertTitle alertStr:gCanNotMakePurchases];
}
// Buy Unlimited Items Button Pressed
}
- (IBAction)restoreCompletedTransactions:(id)sender {
if ([products count] == 0) {
[self loadStore:YES];
[self showAlert:gInAppAlertTitle alertStr:gNoProductsToMakePurchase];
return;
}
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
The code that is passed as a block to handleReply tries call a method on an object that is already released. Without further information than the provided stack trace, there is probably nothing more that can be said.
I am trying to remove ads with In App Purchase but It does not work. When I open my app the banner comes up and when I tap the banner it says it is working correctly. However after a few seconds it disappears and then appears again after a few minutes. Just keeping on the same screen. Is my code correct for this?
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSUserDefaults *saveapp = [NSUserDefaults standardUserDefaults];
bool saved = [saveapp boolForKey:k_Save];
if (!saved) {
/// not save code here
} else {
///saved code here
Label.text = #"item has been purchased";
}
}
-(void)bannerViewDidLoadAd:(ADBannerView *)banner {
NSUserDefaults *saveapp = [NSUserDefaults standardUserDefaults];
bool saved = [saveapp boolForKey:k_Save];
if (!saved) {
/// not save code here
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1];
[banner setAlpha:1];
[UIView commitAnimations];
} else {
///saved code here
}
}
-(void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1];
[banner setAlpha:0];
[UIView commitAnimations];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
This is for saving
- (IBAction)PurchaseItem:(id)sender {
_purchaseController = [[PurchasedViewController alloc] initWithNibName:nil bundle:nil];
_purchaseController.productID = #"com.myname.test.iap1";
[[SKPaymentQueue defaultQueue] addTransactionObserver:_purchaseController];
[self presentViewController:_purchaseController animated:YES completion:NULL];
[_purchaseController getProductID:self];
}
-(void)Purchased {
Label.text = #"item has been purchased";
iadBanner.hidden = YES;
NSUserDefaults *saveapp = [NSUserDefaults standardUserDefaults];
[saveapp setBool:TRUE forKey:k_Save];
[saveapp synchronize];
}
- (IBAction)BuyProduct:(id)sender {
SKPayment *payment = [SKPayment paymentWithProduct:_product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (IBAction)Restore:(id)sender {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
[self UnlockPurchase];
}
-(void)getProductID:(ViewController *)viewController {
_homeViewController = viewController;
if ([SKPaymentQueue canMakePayments]) {
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:self.productID]];
request.delegate = self;
[request start];
} else
_productDescription.text = #"Please enable in app purchase in your settings";
}
#pragma mark _
#pragma mark SKProductsRequestDelegate
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *products = response.products;
if (products.count != 0) {
_product = products[0];
_buyButton.enabled = YES;
_productTitle.text = _product.localizedTitle;
_productDescription.text = _product.localizedDescription;
} else {
_productTitle.text = #"Product Not Found";
}
products = response.invalidProductIdentifiers;
for (SKProduct *product in products) {
NSLog(#"Product not Found: %#", product);
}
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:[self UnlockPurchase];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:NSLog(#"Transaction Failed");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
default:
break;
}
}
}
-(void)UnlockPurchase {
_buyButton.enabled = NO;
[_buyButton setTitle:#"Purchased" forState:UIControlStateDisabled];
[_homeViewController Purchased];
}
Well, at least one potential issue I see is that you try to hide with:
iadBanner.hidden = YES;
But I don't see that property referred to anywhere else. Is it synthesized? Hooked up in IB?
With ios7 use self.canDisplayBannerAds instead , its easier and more straight forward
- (void)viewDidLoad
{
[super viewDidLoad];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
bool adsRemoved = [userDefaults boolForKey:#"removeAds"];
self.canDisplayBannerAds=!adsRemoved;
}
I am trying to make it so that if I buy an item on my app it would allow me to hide the iAd that I have put.
However, when I test it out only my restore button is tappable and I cannot tap the buy button.
Did I miss something with my app?
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSUserDefaults *saveapp = [NSUserDefaults standardUserDefaults];
bool saved = [saveapp boolForKey:k_Save];
if (!saved) {
/// not save code here
} else {
///saved code here
Label.text = #"item has been purchased";
}
}
-(void)bannerViewDidLoadAd:(ADBannerView *)banner {
NSUserDefaults *saveapp = [NSUserDefaults standardUserDefaults];
bool saved = [saveapp boolForKey:k_Save];
if (!saved) {
/// not save code here
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1];
[banner setAlpha:1];
[UIView commitAnimations];
} else {
///saved code here
}
}
-(void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1];
[banner setAlpha:0];
[UIView commitAnimations];
}
- (IBAction)PurchaseItem:(id)sender {
_purchaseController = [[PurchasedViewController alloc] initWithNibName:nil bundle:nil];
_purchaseController.productID = #"com.myname.appname.iap1";
[[SKPaymentQueue defaultQueue] addTransactionObserver:_purchaseController];
[self presentViewController:_purchaseController animated:YES completion:NULL];
[_purchaseController getProductID:self];
}
-(void)Purchased {
Label.text = #"item has been purchased";
iadBanner.hidden = YES;
NSUserDefaults *saveapp = [NSUserDefaults standardUserDefaults];
[saveapp setBool:TRUE forKey:k_Save];
[saveapp synchronize];
}
Purchase View
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
_buyButton.enabled = NO;
}
- (IBAction)BuyProduct:(id)sender {
SKPayment *payment = [SKPayment paymentWithProduct:_product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (IBAction)Restore:(id)sender {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
[self UnlockPurchase];
}
-(void)getProductID:(ViewController *)viewController {
_homeViewController = viewController;
if ([SKPaymentQueue canMakePayments]) {
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:self.productID]];
request.delegate = self;
[request start];
} else
_productDescription.text = #"Please enable in app purchase in your settings";
}
#pragma mark _
#pragma mark SKProductsRequestDelegate
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *products = response.products;
if (products.count != 0) {
_product = products[0];
_buyButton.enabled = YES;
_productTitle.text = _product.localizedTitle;
_productDescription.text = _product.localizedDescription;
} else {
_productTitle.text = #"Product Not Found";
}
products = response.invalidProductIdentifiers;
for (SKProduct *product in products) {
NSLog(#"Product not Found: %#", product);
}
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:[self UnlockPurchase];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:NSLog(#"Transaction Failed");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
default:
break;
}
}
}
-(void)UnlockPurchase {
_buyButton.enabled = NO;
[_buyButton setTitle:#"Purchased" forState:UIControlStateDisabled];
[_homeViewController Purchased];
}
You are explicitly disabling it with: _buyButton.enabled = NO; on viewDidLoad and are only setting it enabled when you receive products.. which you can only do by tapping the button in the first place.
I am confused about how to and when to tell the user that they completed the purchase successfully. I got my application rejected during the app review process for this reason:
1. Launch app
2. Tap on learn about the benefits of subscription
3. Tap on Subscribe
4. Tap on Confirm and enter iTunes password
5. No further action occurs
And I am not sure when and how to tell the user they entered their info correctly since that is confirmed on the iTunes server.
I have an IAPHelper class which looks like this:
//
// IAPHelper.m
// BusinessPlan
//
// Created by MacOSLion on 8/12/13.
//
//
// 1
#import "IAPHelper.h"
#import <StoreKit/StoreKit.h>
// 2
//#interface IAPHelper () <SKProductsRequestDelegate>
#interface IAPHelper () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
#end
#implementation IAPHelper
{
// 3
SKProductsRequest * _productsRequest;
// 4
RequestProductsCompletionHandler _completionHandler;
NSSet * _productIdentifiers;
NSMutableSet * _purchasedProductIdentifiers;
}
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers
{
if ((self = [super init]))
{
// Store product identifiers
_productIdentifiers = productIdentifiers;
// Check for previously purchased products
_purchasedProductIdentifiers = [NSMutableSet set];
for (NSString * productIdentifier in _productIdentifiers)
{
BOOL productPurchased = [[NSUserDefaults standardUserDefaults] boolForKey:productIdentifier];
if (productPurchased)
{
[_purchasedProductIdentifiers addObject:productIdentifier];
NSLog(#"Previously purchased: %#", productIdentifier);
// SET memory to yes and then use that later.
// Get user data.
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
// First time on the app, so set the user cookie.
[standardUserDefaults setBool:YES forKey:#"subscriber"];
// Saving
[[NSUserDefaults standardUserDefaults] synchronize];
}
else
{
NSLog(#"Not purchased: %#", productIdentifier);
}
}
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
// retrieve the product information from iTunes Connect
- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler
{
// 1
_completionHandler = [completionHandler copy];
// 2
_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];
_productsRequest.delegate = self;
[_productsRequest start];
}
#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSLog(#"Loaded list of products...");
_productsRequest = nil;
NSArray * skProducts = response.products;
for (SKProduct * skProduct in skProducts)
{
NSLog(#"Found product: %# %# %0.2f",
skProduct.productIdentifier,
skProduct.localizedTitle,
skProduct.price.floatValue);
}
_completionHandler(YES, skProducts);
_completionHandler = nil;
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(#"Failed to load list of products.");
_productsRequest = nil;
_completionHandler(NO, nil);
_completionHandler = nil;
}
- (BOOL)productPurchased:(NSString *)productIdentifier
{
return [_purchasedProductIdentifiers containsObject:productIdentifier];
}
- (void)buyProduct:(SKProduct *)product
{
NSLog(#"Buying %#...", product.productIdentifier);
SKPayment * payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction * transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
default:
break;
}
};
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
NSLog(#"completeTransaction...");
[self provideContentForProductIdentifier:transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
// SET memory to yes and then use that later.
// Get user data.
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
// First time on the app, so set the user cookie.
[standardUserDefaults setBool:YES forKey:#"subscriber"];
// Saving
[[NSUserDefaults standardUserDefaults] synchronize];
// Tell user that things are purchased.
// MESSAGE PERSON THAT CAN'T CONNECT TO SERVER
// UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Success sending purchase request."
// message:#"Just press OK and wait a few moments while iTunes processes the request." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
//
// [message show];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
NSLog(#"restoreTransaction...");
[self provideContentForProductIdentifier:transaction.originalTransaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
NSLog(#"failedTransaction...");
if (transaction.error.code != SKErrorPaymentCancelled)
{
NSLog(#"Transaction error: %#", transaction.error.localizedDescription);
// MESSAGE PERSON THAT CAN'T CONNECT TO SERVER
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Could not complete your transaction"
message:#"Please try again. If the error persists, please email support at: alex#problemio.com" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
// Add to top of file
NSString *const IAPHelperProductPurchasedNotification = #"IAPHelperProductPurchasedNotification";
// Add new method
- (void)provideContentForProductIdentifier:(NSString *)productIdentifier
{
//NSLog(#"Provifing content for subsciber: ");
// MESSAGE PERSON THAT CAN'T CONNECT TO SERVER
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Subscribed successfully!"
message:#"Now you can ask questions right on the app, and get our monthly business content." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
[_purchasedProductIdentifiers addObject:productIdentifier];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:productIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:IAPHelperProductPurchasedNotification object:productIdentifier userInfo:nil];
}
#end
And my class from which I start the transaction process:
#import "SubscriptionController.h"
// 1
#import "RageIAPHelper.h"
#import <StoreKit/StoreKit.h>
// 2
#interface SubscriptionController ()
{
NSArray *_products;
// Add new instance variable to class extension
NSNumberFormatter * _priceFormatter;
}
#end
#implementation SubscriptionController
// 3
- (void)viewDidLoad
{
[super viewDidLoad];
//self.refreshControl = [[UIRefreshControl alloc] init];
//[self.refreshControl addTarget:self action:#selector(reload) forControlEvents:UIControlEventValueChanged];
[self reload];
//[self.refreshControl beginRefreshing];
// Add to end of viewDidLoad
_priceFormatter = [[NSNumberFormatter alloc] init];
[_priceFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[_priceFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
self.view.backgroundColor = [UIColor colorWithWhite:0.859 alpha:1.000];
}
// 4
- (void)reload
{
_products = nil;
//[self.tableView reloadData];
[[RageIAPHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products)
{
if (success)
{
_products = products;
//[self.tableView reloadData];
}
//[self.refreshControl endRefreshing];
}];
}
#pragma mark - Table View
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
// 5
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _products.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"a");
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
SKProduct * product = (SKProduct *) _products[indexPath.row];
cell.textLabel.text = product.localizedTitle;
// Add to bottom of tableView:cellForRowAtIndexPath (before return cell)
[_priceFormatter setLocale:product.priceLocale];
cell.detailTextLabel.text = [_priceFormatter stringFromNumber:product.price];
if ([[RageIAPHelper sharedInstance] productPurchased:product.productIdentifier])
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
cell.accessoryView = nil;
}
else
{
UIButton *buyButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
buyButton.frame = CGRectMake(0, 0, 72, 37);
[buyButton setTitle:#"Buy" forState:UIControlStateNormal];
buyButton.tag = indexPath.row;
[buyButton addTarget:self action:#selector(buyButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
cell.accessoryType = UITableViewCellAccessoryNone;
cell.accessoryView = buyButton;
}
return cell;
}
//- (IBAction)subscribe:(id)sender
//{
// UIButton *buyButton = (UIButton *)sender;
// SKProduct *product = _products[buyButton.tag];
//
// NSLog(#"Buying %#...", product.productIdentifier);
// [[RageIAPHelper sharedInstance] buyProduct:product];
//}
- (void)viewWillAppear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(productPurchased:) name:IAPHelperProductPurchasedNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)productPurchased:(NSNotification *)notification
{
NSLog(#"PURCHASEDDDDDDDDD");
// NSString * productIdentifier = notification.object;
// [_products enumerateObjectsUsingBlock:^(SKProduct * product, NSUInteger idx, BOOL *stop)
// {
// if ([product.productIdentifier isEqualToString:productIdentifier])
// {
// // TODO:
// // Update how the button appears.
//
//
//// [self.table reloadRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:idx inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
// *stop = YES;
// }
// }];
// MESSAGE PERSON THAT CAN'T CONNECT TO SERVER
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Purchased successfully"
message:#":)" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
// PUSH TO CONFIRMATION
}
//- (IBAction)subscribe:(id)sender
//{
//
//}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (IBAction)createSub:(id)sender
{
UIButton *buyButton = (UIButton *)sender;
SKProduct *product = _products[buyButton.tag];
if ( product == nil)
{
// MESSAGE PERSON THAT CAN'T CONNECT TO SERVER
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Pulling product data from iTunes..."
message:#"Please try again in a few moments." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
}
else
{
// MESSAGE PERSON THAT CAN'T CONNECT TO SERVER
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Success sending purchase request."
message:#"Just press OK and wait a few moments while iTunes processes the request." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
NSLog(#"Buying %#...", product.productIdentifier);
[[RageIAPHelper sharedInstance] buyProduct:product];
}
}
#end
Thank you for your help!
You should have some sort of UI update to tell the user that the payment was successful and the feature is now available/unlocked. Typically, this is done either with an update in your views to correspond to the new content, or a UIAlertView if there are no visual changes made.