I'm looking through the internet for days already and can not find solution to my problem. I have created an app and want you to remove the advertising through an in app purchase. My problem is that the app sometimes crashes when I click on the button "BuyProduct". I then get the error message "Thread 1: EXC_BAD_ACCESS (code = 1, address = 0x16e6f6980)" after the line [[SKPaymentQueue defaultQueue] addPayment:payment];.
Here my .h of my PurchasedViewController:
#import <UIKit/UIKit.h>
#import <StoreKit/StoreKit.h>
#interface PurchasedViewController2 : UIViewController <SKPaymentTransactionObserver, SKProductsRequestDelegate>{
NSTimer *myTimer;
int countdown;
}
#property (strong, nonatomic) SKProduct *product;
#property (strong, nonatomic) NSString *productID;
#property (strong, nonatomic) IBOutlet UILabel *productTitle;
#property (strong, nonatomic) IBOutlet UITextView *productDescription;
#property (strong, nonatomic) IBOutlet UIButton *buyButton;
#property (weak, nonatomic) IBOutlet UIButton *goBack;
#property (weak, nonatomic) IBOutlet UILabel *labelGray;
#property (weak, nonatomic) IBOutlet UILabel *labelCountdown;
- (IBAction)GoBack:(id)sender;
- (IBAction)BuyProduct:(id)sender;
- (IBAction)Restore:(id)sender;
-(void)getProductID:(UIViewController *)viewController;
-(void)UnlockPurchase;
-(void)update;
#end
here the .m
#import "PurchasedViewController2.h"
#import "ViewController.h"
#interface PurchasedViewController2 ()
#property (strong, nonatomic) ViewController *homeViewController;
#end
#implementation PurchasedViewController2
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
countdown = 5;
_labelCountdown.text = [NSString stringWithFormat:#"%i",countdown];
_goBack.enabled = FALSE;
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(update) userInfo:nil repeats:YES];
// Do any additional setup after loading the view.
}
-(void)update{
countdown--;
if (countdown >=0) {
_labelCountdown.text = [NSString stringWithFormat:#"%i",countdown];
}
if (countdown <=0) {
_labelCountdown.hidden = TRUE;
_labelGray.hidden = TRUE;
_goBack.enabled = TRUE;
[myTimer invalidate];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
- (IBAction)GoBack:(id)sender {
[self dismissViewControllerAnimated:YES completion:NULL];
}
- (IBAction)BuyProduct:(id)sender {
SKPayment *payment = [SKPayment paymentWithProduct:_product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
-(void)viewDidUnload{
[[SKPaymentQueue defaultQueue]removeTransactionObserver:self];
[super viewDidUnload];
}
- (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 setting";
}
}
#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];
}
#end
and here is my call of the view:
- (IBAction)PurchaseItem:(id)sender {
_purchaseController = [[PurchasedViewController2 alloc] initWithNibName:nil bundle:nil];
_purchaseController.productID =#"PS.PileUp.RemoveAds";
[[SKPaymentQueue defaultQueue] addTransactionObserver:_purchaseController];
[self presentViewController:_purchaseController animated:YES completion:NULL];
[_purchaseController getProductID:self];
}
Thank you very munch
Thx i solve it. I change the GoBack Method to:
- (IBAction)GoBack:(id)sender {
[[SKPaymentQueue defaultQueue]removeTransactionObserver:self];
[self dismissViewControllerAnimated:YES completion:NULL];
}
It looks like you have a race condition between pressing the BuyProduct button and creation of the _product. In general, you should be finished the SKProductsRequest before allowing the user to tap BuyProduct. That is, do you know _product has the value you want when BuyProduct is pressed?
Related
Please be patient. This is my first time setting up an in-app purchase and I will try and provide as much information as I can. Instead of grooming the internet for someone else's code I ended up purchasing a class from infinite skills specifically on how to add a non-renewing subscription in-app purchase to my app. Their class was outdated, and come to find out, the "follow along" project files did not download. So I did a lot of research and this is what I came up with:
I created an in-app purchase in itunes, the in-app identifier matches the identifier in my .m coding.
I created a new provisioning profile and enabled in-app purchases and the icloud to manage the backend for the purchase. When I returned to the app i enabled icloud and made sure in-app purchases was turned on in the project target.
I created a view controller, added buttons and for the subclass I used SKStoreProductViewController. That View Controller looks like this:
I imported the storekit and the SK delegates.
It crashes when I hit the tabbarbutton from my home view to bring me to the in-app view controller.
Finally the coding:
InAppViewController.h:
#import <StoreKit/StoreKit.h>
#interface InAppViewController : SKStoreProductViewController
#end
InAppViewController.m:
//
// InAppViewController.m
// Contractor Rich
//
// Created by Joshua Hart on 2/1/15.
// Copyright (c) 2015 Code By Hart. All rights reserved.
//
#import "InAppViewController.h"
#import <StoreKit/StoreKit.h>
#interface InAppViewController ()<SKProductsRequestDelegate, SKPaymentTransactionObserver>
#property (weak, nonatomic) IBOutlet UIButton *btnBuyAccess;
#property (weak, nonatomic) IBOutlet UIButton *btnPremiumFeature;
#property (weak, nonatomic) IBOutlet UILabel *lblStatus;
#property (strong, nonatomic) SKProduct *product;
#property (strong, nonatomic) NSUbiquitousKeyValueStore *keyStore;
#property (strong, nonatomic) NSDate *expirationDate;
- (IBAction)btnBuyAccessTouched: (id)sender;
-(void)getProductInfo;
#end
#implementation InAppViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
_btnBuyAccess.userInteractionEnabled = NO;
_keyStore = [[NSUbiquitousKeyValueStore alloc] init];
_expirationDate = [_keyStore objectForKey:#"expirationDate"];
NSDate *today = [NSDate date];
if (_expirationDate == nil)
_expirationDate = today;
if (_expirationDate > today) {
_btnPremiumFeature.userInteractionEnabled = YES;
}
else {
_btnBuyAccess.userInteractionEnabled = YES;
[self getProductInfo];
}
}
-(void) getProductInfo{
if ([SKPaymentQueue canMakePayments])
{
NSMutableArray *productIdentifierList = [[NSMutableArray alloc] init];
[productIdentifierList addObject:[NSString stringWithFormat:#"com.joshua.contractorrich.inapp"]];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithArray:productIdentifierList]];
request.delegate = self;
[request start];
}
}
-(void) productsRequest: (SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray *products = response.products;
if (products.count != 0)
{
_product = products[0];
_btnBuyAccess.userInteractionEnabled = YES;
_lblStatus.text = #"Ready for Purchase!";
}else{
_lblStatus.text = #"Product was not Found!";
}
products = response.invalidProductIdentifiers;
for (SKProduct *product in products)
{
NSLog(#"Product not found: %#", product);
}
}
- (IBAction)btnBuyAccessTouched: (id)sender {
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:
_btnPremiumFeature.userInteractionEnabled = YES;
_lblStatus.text = #"Purchase Completed!";
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed: NSLog(#"Transaction Failed!");
_lblStatus.text = #"Purchase Failed!";
[[SKPaymentQueue defaultQueue]
finishTransaction:transaction];
default:
break;
}
}
}
-(void) setExpirationDate {
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setMonth:3];
NSDate *expirationDate = [[NSCalendar currentCalendar] dateByAddingComponents:components toDate:[NSDate date] options:0];
[_keyStore setObject:expirationDate forKey:#"expirationDate"];
[_keyStore synchronize];
}
-(void) initializeStore {
[_keyStore setObject:nil forKey:#"expirationDate"];
[_keyStore synchronize];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#end
Please understand this is my first time trying this. What may seem stupid is still the learning phase for me. Thank you!
There is no runtime error in this story. All that's happened is that you've created an Exceptions breakpoint. Now you are pausing at it. Simply resume running. If this is troublesome, disable the Exceptions breakpoint.
I am getting my In-App Purchase Title (I can see it in NSLog), but the UILabel that shows the title gets called after viewDidLoad. Is there any way I can get the Title before viewDidLoad, so that the UILabel will show the Title?
Below is my code. Let me know if you need me to post anything more, thanks!
PurchaseViewController.h
#interface PurchaseViewController : UIViewController
<SKPaymentTransactionObserver, SKProductsRequestDelegate>
#property (strong, nonatomic) SKProduct *product;
#property (strong, nonatomic) NSString *productID;
#property (strong, nonatomic) IBOutlet UILabel *productTitle;
#property (strong, nonatomic) IBOutlet UIButton *buyButton;
#property (strong, nonatomic) IBOutlet UITextView *productDescription;
- (IBAction)buyProduct:(id)sender;
- (void)getProductInfo:(UIViewController *)viewController;
#property (nonatomic) int buttonNumber;
#end
PurchaseViewController.m
#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);
}
}
- (IBAction)buyProduct:(id)sender {
SKPayment *payment = [SKPayment paymentWithProduct:_product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
#pragma mark SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
[self unlockFeature];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(#"Transaction Failed");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
default:
break;
}
}
}
The usual architecture is to get the product info before showing the view containing the info. Then you pass that info to your view controller as you create the view controller and show its view.
Moreover, you shouldn't handle these things in any view controller code. Fetching product infos and similar stuff like that (Location, Session, app's models' factories etc.) is a typical use case of shared data for a (optionally stateful) Manager singleton (f.e. PurchaseManager in this case) which would be called on app launch/AppDelegate methods and work in the background, independently on your UI code/threads.
Example:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[[PurchaseManager defaultManager] fetchProducts];
}
i have carried out these several steps for five non consumable in app purchases to be available within my app but still nothing is showing up when i go to the TableViewController that i linked the IAP code to... (i would also like to give a big thanks to the tutorial i followed which has got me this far by raywenderlich)
-Made a new app ID
-Successfully followed all the steps for linking and downloading certificates
Changed the bundle ID on xcode for my project to the one i made for
my app on IOS developer portal
-Linked all the devices and made a test user account
-Made the non consumable IAP on itunes connect and used the Identifiers within the IAP coding
-Signed my phone out of my itunes account so tht its ready to be used with a test account
-Coding for the IAP looks to be correct with store kit imported and product identifiers implemented within the .m file
-Waited 24 hours for the newly created IAP to sync with itunes connect
-I DIDNT upload the binaries!
i have hosting with apple turned on but didnt upload anything - could
this be the issue?
i deleted the app on my phone and reinstalled for testing but still
nothing
All i get when i successfull run the build is a page that looks like its loading something but then nothing a blank page that i can pull down to refresh on but still nothing no IAP that i created.
Any suggestions of what else i can do or add and if you need me to upload all the codes for all the files i can do...
coding for IAP within the ViewController.h file
#import "Accounts/Accounts.h"
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
#interface ViewController19 : UITableViewController
#end
Coding for IAP within the ViewController.m file
#import "DetailViewController.h"
#import "SecretsIAPHelper.h"
#import <StoreKit/StoreKit.h>
#interface ViewController19 () {
NSArray *_products;
NSNumberFormatter * _priceFormatter;
}
#end
#implementation ViewController19
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"XXXXXXXXXXXXX";
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:#selector(reload) forControlEvents:UIControlEventValueChanged];
[self reload];
[self.refreshControl beginRefreshing];
_priceFormatter = [[NSNumberFormatter alloc] init];
[_priceFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[_priceFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Restore" style:UIBarButtonItemStyleBordered target:self action:#selector(restoreTapped:)];
}
- (void)restoreTapped:(id)sender {
[[SecretsIAPHelper sharedInstance] restoreCompletedTransactions];
}
- (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 {
NSString * productIdentifier = notification.object;
[_products enumerateObjectsUsingBlock:^(SKProduct * product, NSUInteger idx, BOOL *stop) {
if ([product.productIdentifier isEqualToString:productIdentifier]) {
[self.tableView reloadRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:idx inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
*stop = YES;
}
}];
}
- (void)reload {
_products = nil;
[self.tableView reloadData];
[[SecretsIAPHelper 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;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _products.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
SKProduct * product = (SKProduct *) _products[indexPath.row];
cell.textLabel.text = product.localizedTitle;
[_priceFormatter setLocale:product.priceLocale];
cell.detailTextLabel.text = [_priceFormatter stringFromNumber:product.price];
if ([[SecretsIAPHelper 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;
}
- (void)buyButtonTapped:(id)sender {
UIButton *buyButton = (UIButton *)sender;
SKProduct *product = _products[buyButton.tag];
NSLog(#"Buying %#...", product.productIdentifier);
[[SecretsIAPHelper sharedInstance] buyProduct:product];
}
#end
Coding for IAP within the IAPHelper.h file
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
UIKIT_EXTERN NSString *const IAPHelperProductPurchasedNotification;
typedef void (^RequestProductsCompletionHandler)(BOOL success, NSArray * products);
#interface IAPHelper : NSObject
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers;
- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler;
- (void)buyProduct:(SKProduct *)product;
- (BOOL)productPurchased:(NSString *)productIdentifier;
- (void)restoreCompletedTransactions;
#end
Coding for IAP within the IAPHelper.m file
#import "IAPHelper.h"
#import <StoreKit/StoreKit.h>
NSString *const IAPHelperProductPurchasedNotification = #"IAPHelperProductPurchasedNotification";
// 2
#interface IAPHelper () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
#end
// 3
#implementation IAPHelper {
SKProductsRequest * _productsRequest;
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);
} else {
NSLog(#"Not purchased: %#", productIdentifier);
}
}
// Add self as transaction observer
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {
// 1
_completionHandler = [completionHandler copy];
// 2
_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];
_productsRequest.delegate = self;
[_productsRequest start];
}
- (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];
}
#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;
}
#pragma mark SKPaymentTransactionOBserver
- (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];
}
- (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);
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
- (void)provideContentForProductIdentifier:(NSString *)productIdentifier {
[_purchasedProductIdentifiers addObject:productIdentifier];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:productIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:IAPHelperProductPurchasedNotification object:productIdentifier userInfo:nil];
}
- (void)restoreCompletedTransactions {
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
#end
Coding for IAP within the SecretsIAPHelper.h file
#import "IAPHelper.h"
#interface SecretsIAPHelper : IAPHelper
+ (SecretsIAPHelper *)sharedInstance;
#end
Coding for IAP within the SecretsIAPHelper.m file
#import "SecretsIAPHelper.h"
#implementation SecretsIAPHelper
+ (SecretsIAPHelper *)sharedInstance {
static dispatch_once_t once;
static SecretsIAPHelper * sharedInstance;
dispatch_once(&once, ^{
NSSet * productIdentifiers = [NSSet setWithObjects:
#"com.designsbydeondrae.XXXXXXX.remove_ads",
#"com.designsbydeondrae.XXXXXXX.FoundationSkills",
#"com.designsbydeondrae.XXXXXXX.IntermediateSkills",
#"com.designsbydeondrae.XXXXXXX.AllSkills",
#"com.designsbydeondrae.XXXXXXX.AdvancedSkills",
nil];
sharedInstance = [[self alloc] initWithProductIdentifiers:productIdentifiers];
});
return sharedInstance;
}
#end
Coding for IAP within the DetailViewController.h file
#import <UIKit/UIKit.h>
#interface DetailViewController : UIViewController
#property (strong, nonatomic) id detailItem;
#property (weak, nonatomic) IBOutlet UILabel *detailDescriptionLabel;
#end
Coding for IAP within the DetailViewController.h file
#import "DetailViewController.h"
#interface DetailViewController ()
- (void)configureView;
#end
#implementation DetailViewController
#pragma mark - Managing the detail item
- (void)setDetailItem:(id)newDetailItem
{
if (_detailItem != newDetailItem) {
_detailItem = newDetailItem;
// Update the view.
[self configureView];
}
}
- (void)configureView
{
// Update the user interface for the detail item.
if (self.detailItem) {
self.detailDescriptionLabel.text = [self.detailItem description];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self configureView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
So just to summarise...
Used StoreKit to access the In-App Purchase APIs and retrieve the
list which brings up nothing when i run the build
Specified the product identifiers for my app
Displaying the products which seems to be an issue though it does
show a screen like its about to load something then nothing just a
page with a pull to refresh on it
For any one that finds this post in the future, the reason no items where showing up when requesting IAP was due to content not being uploaded for hosted content.
I'm struggling for couple of days now to sort this thing out and simply just can't find the way. I want to play audio in background when app exits or when i click on link to go to Safari, but i just wont go to background mode. Please help.
FirstViewController.h file :
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVAudioPlayer.h>
#import <AVFoundation/AVFoundation.h>
#interface RygestopFirstViewController : UIViewController <AVAudioPlayerDelegate> {
IBOutlet UIButton *playButton;
IBOutlet UISlider *volumeSlider;
IBOutlet UISlider *progressBar;
IBOutlet UILabel *currentTime;
IBOutlet UILabel *duration;
AVAudioPlayer *player;
UIImage *playBtnBG;
UIImage *pauseBtnBG;
NSTimer *updateTimer;
BOOL inBackground;
}
- (IBAction)playButtonPressed:(UIButton*)sender;
- (IBAction)volumeSliderMoved:(UISlider*)sender;
- (IBAction)progressSliderMoved:(UISlider*)sender;
#property (nonatomic, retain) UIButton *playButton;
#property (nonatomic, retain) UISlider *volumeSlider;
#property (nonatomic, retain) UISlider *progressBar;
#property (nonatomic, retain) UILabel *currentTime;
#property (nonatomic, retain) UILabel *duration;
#property (nonatomic, retain) NSTimer *updateTimer;
#property (nonatomic, assign) AVAudioPlayer *player;
#property (nonatomic, assign) BOOL inBackground;
#end
FirstViewController.m code:
// amount to skip on rewind or fast forward
#define SKIP_TIME 1.0
// amount to play between skips
#define SKIP_INTERVAL .2
#implementation RygestopFirstViewController
#synthesize playButton;
#synthesize volumeSlider;
#synthesize progressBar;
#synthesize currentTime;
#synthesize duration;
#synthesize updateTimer;
#synthesize player;
#synthesize inBackground;
-(void)updateCurrentTimeForPlayer:(AVAudioPlayer *)p
{
currentTime.text = [NSString stringWithFormat:#"%d:%02d", (int)p.currentTime / 60, (int)p.currentTime % 60, nil];
progressBar.value = p.currentTime;
}
- (void)updateCurrentTime
{
[self updateCurrentTimeForPlayer:self.player];
}
- (void)updateViewForPlayerState:(AVAudioPlayer *)p
{
[self updateCurrentTimeForPlayer:p];
if (updateTimer)
[updateTimer invalidate];
if (p.playing)
{
[playButton setImage:((p.playing == YES) ? pauseBtnBG : playBtnBG) forState:UIControlStateNormal];
updateTimer = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:#selector(updateCurrentTime) userInfo:p repeats:YES];
}
else
{
[playButton setImage:((p.playing == YES) ? pauseBtnBG : playBtnBG) forState:UIControlStateNormal];
updateTimer = nil;
}
}
- (void)updateViewForPlayerStateInBackground:(AVAudioPlayer *)p
{
[self updateCurrentTimeForPlayer:p];
if (p.playing)
{
[playButton setImage:((p.playing == YES) ? pauseBtnBG : playBtnBG) forState:UIControlStateNormal];
}
else
{
[playButton setImage:((p.playing == YES) ? pauseBtnBG : playBtnBG) forState:UIControlStateNormal];
}
}
-(void)updateViewForPlayerInfo:(AVAudioPlayer*)p
{
duration.text = [NSString stringWithFormat:#"%d:%02d", (int)p.duration / 60, (int)p.duration % 60, nil];
progressBar.maximumValue = p.duration;
volumeSlider.value = p.volume;
}
-(void)pausePlaybackForPlayer:(AVAudioPlayer*)p
{
[p pause];
[self updateViewForPlayerState:p];
}
-(void)startPlaybackForPlayer:(AVAudioPlayer*)p
{
if ([p play])
{
[self updateViewForPlayerState:p];
}
else
NSLog(#"Could not play %#\n", p.url);
}
- (IBAction)playButtonPressed:(UIButton *)sender
{
if (player.playing == YES)
[self pausePlaybackForPlayer: player];
else
[self startPlaybackForPlayer: player];
}
- (IBAction)volumeSliderMoved:(UISlider *)sender
{
player.volume = [sender value];
}
- (IBAction)progressSliderMoved:(UISlider *)sender
{
player.currentTime = sender.value;
[self updateCurrentTimeForPlayer:player];
}
- (void)dealloc
{
[super dealloc];
[playButton release];
[volumeSlider release];
[progressBar release];
[currentTime release];
[duration release];
[updateTimer release];
[player release];
[playBtnBG release];
[pauseBtnBG release];
}
#pragma mark AVAudioPlayer delegate methods
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)p successfully:(BOOL)flag
{
if (flag == NO)
NSLog(#"Playback finished unsuccessfully");
[p setCurrentTime:0.];
if (inBackground)
{
[self updateViewForPlayerStateInBackground:p];
}
else
{
[self updateViewForPlayerState:p];
}
}
- (void)playerDecodeErrorDidOccur:(AVAudioPlayer *)p error:(NSError *)error
{
NSLog(#"ERROR IN DECODE: %#\n", error);
}
// we will only get these notifications if playback was interrupted
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)p
{
NSLog(#"Interruption begin. Updating UI for new state");
// the object has already been paused, we just need to update UI
if (inBackground)
{
[self updateViewForPlayerStateInBackground:p];
}
else
{
[self updateViewForPlayerState:p];
}
}
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)p
{
NSLog(#"Interruption ended. Resuming playback");
[self startPlaybackForPlayer:p];
}
#pragma mark background notifications
- (void)registerForBackgroundNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(setInBackgroundFlag)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(clearInBackgroundFlag)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
- (void)setInBackgroundFlag
{
inBackground = true;
}
- (void)clearInBackgroundFlag
{
inBackground = false;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = NSLocalizedString(#"Play", #"First");
self.tabBarItem.image = [UIImage imageNamed:#"Home"];
}
return self;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
//Make sure we can recieve remote control events
- (BOOL)canBecomeFirstResponder {
return YES;
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
playBtnBG = [[UIImage imageNamed:#"Player.png"] retain];
pauseBtnBG = [[UIImage imageNamed:#"Pause.png"] retain];
[playButton setImage:playBtnBG forState:UIControlStateNormal];
[self registerForBackgroundNotifications];
updateTimer = nil;
duration.adjustsFontSizeToFitWidth = YES;
currentTime.adjustsFontSizeToFitWidth = YES;
progressBar.minimumValue = 0.0;
// Load the the sample file, use mono or stero sample
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: [[NSBundle mainBundle] pathForResource:#"Sound1" ofType:#"m4a"]];
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
if (self.player)
{
[self updateViewForPlayerInfo:player];
[self updateViewForPlayerState:player];
player.numberOfLoops = 0;
player.delegate = self;
}
[fileURL release];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
And, by the way, i want to run thin in tab bar application, so the background mode must be present always.
Here's what you're looking for: https://devforums.apple.com/message/264397 and set your background mode to 'App plays audio' in your app's .plist file.
I try to make in app purchase on my app. But I have not result when I try to run it in device.
My code is following:
This is object needed for get info about products and purchase it.
.h
#import <Foundation/Foundation.h>
#import "StoreKit/StoreKit.h"
#define kProductsLoadedNotification #"ProductsLoaded"
#define kProductPurchasedNotification #"ProductPurchased"
#define kProductPurchaseFailedNotification #"ProductPurchaseFailed"
#interface StoreManager : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
#property (strong, nonatomic) NSMutableSet * _purchasedProducts;
#property (strong, nonatomic) NSArray *products;
#property (strong, nonatomic) NSSet *_productIdentifiers;
#property (strong, nonatomic) SKProductsRequest *request;
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers;
- (void)requestProducts;
- (void)buyProduct:(SKProduct *)product;
- (void)completeTransaction:(SKPaymentTransaction *)transaction;
- (void)restoreTransaction:(SKPaymentTransaction *)transaction;
- (void)failedTransaction:(SKPaymentTransaction *)transaction;
- (void)provideContent:(NSString *)productIdentifier;
- (void)recordTransaction:(SKPaymentTransaction *)transaction;
#end
.m
#import "StoreManager.h"
#implementation StoreManager
#synthesize _purchasedProducts;
#synthesize products;
#synthesize _productIdentifiers;
#synthesize request;
// Initializes the request object with the set of product identifiers.
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers{
self = [super init];
if(self){
self._productIdentifiers = productIdentifiers;
NSMutableSet *purchased = [NSMutableSet set];
for(NSString * productId in self._productIdentifiers){
BOOL flag = [[NSUserDefaults standardUserDefaults] boolForKey:productId];
if(flag){
[purchased addObject:productId];
NSLog(#"Previously purchased: %#", productId);
}
NSLog(#"Not purchased: %#", productId);
}
self._purchasedProducts = purchased;
}
return self;
}
// Request info for the product.
- (void)requestProducts{
self.request = [[SKProductsRequest alloc] initWithProductIdentifiers:self._productIdentifiers];
self.request.delegate = self;
[self.request start];
}
// Request fail.
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
NSLog(#"Fail request! Error: %#", error);
}
// Delegate method - did receive responce.
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
self.products = response.products;
self.request = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:kProductsLoadedNotification object:self.products];
}
// Buy product.
- (void)buyProduct:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
// Payment transactions
- (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)recordTransaction:(SKPaymentTransaction *)transaction {
// TODO: Record the transaction on the server side...
}
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
[self recordTransaction: transaction];
[self provideContent: transaction.payment.productIdentifier];
NSLog(#"completeTransaction...");
}
- (void)provideContent:(NSString *)productIdentifier {
NSLog(#"Toggling flag for: %#", productIdentifier);
[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:productIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
[self._purchasedProducts addObject:productIdentifier];
[[NSNotificationCenter defaultCenter] postNotificationName:kProductPurchasedNotification object:productIdentifier];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
NSLog(#"restoreTransaction...");
[self recordTransaction: transaction];
[self provideContent: transaction.originalTransaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
if (transaction.error.code != SKErrorPaymentCancelled)
{
NSLog(#"Transaction error: %#", transaction.error.localizedDescription);
}
[[NSNotificationCenter defaultCenter] postNotificationName:kProductPurchaseFailedNotification object:transaction];
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
#end
Singleton object
.h
#import <Foundation/Foundation.h>
#import "StoreManager.h"
#interface StoreWrapper : StoreManager
+ (StoreWrapper *)sharedInstance;
#end
.m
#import "StoreWrapper.h"
#implementation StoreWrapper
static StoreWrapper *gInstance = NULL;
- (id)init{
NSSet *productId = [NSSet setWithObjects:#"com.company.sb.pack1", #"com.company.sb.pack5", nil];
self = [super initWithProductIdentifiers:productId];
if(self){
}
return self;
}
+ (StoreWrapper *)sharedInstance{
if(gInstance == NULL){
gInstance = [[self alloc] init];
}
return gInstance;
}
#end
In the my view controller i call next:
- (void)viewWillAppear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(productsLoaded:) name:kProductsLoadedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(productPurchased:) name:kProductPurchasedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector: #selector(productPurchaseFailed:) name:kProductPurchaseFailedNotification object: nil];
Reachability *reach = [Reachability reachabilityForInternetConnection];
NetworkStatus netStatus = [reach currentReachabilityStatus];
if (netStatus == NotReachable) {
NSLog(#"No internet connection!");
} else {
NSLog(#"Internet connection available!");
// Request product
[[StoreWrapper sharedInstance] requestProducts];
[activIndicator startAnimating];
}
[super viewWillAppear:animated];
}
for this point I send an request for get product info from server with my product id
next if product loaded my class get notification about this (and call following method).
- (void)productsLoaded:(NSNotification *)notification{
[activIndicator stopAnimating];
[activIndicator setHidden:YES];
NSArray *arr = [notification object];
for(int i = 0; i < [arr count]; i++){
SKProduct *product = [[StoreWrapper sharedInstance].products objectAtIndex:i];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(0, i*55, 100, 50)];
[btn setTitle:product.localizedTitle forState:UIControlStateNormal];
[btn setTag:i];
[btn addTarget:self action:#selector(buyProduct:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
}
For the button i add selector buyProduct (as shown above)
- (void)buyProduct:(id)sender{
NSLog(#"sender tag %d", [sender tag]);
int tag = [sender tag];
SKProduct *product = [[StoreWrapper sharedInstance].products objectAtIndex:tag];
[[StoreWrapper sharedInstance] buyProduct:product];
}
For iPhone Simulator this work wonderful, but on device I don have any button, because response.products not download.
All method are called, but delegate method *- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse )response not return response.products.
Thanks for your time and thanks for answer!
For resolving, this problem I reset my device and rerun the application.
This helps for me.