My Code
#define kMyFeatureIdentifier #"com.yourcompany.FussballQuiz"
- (void) initializeStore
{
SKProductsRequest *request= [[SKProductsRequest alloc]
initWithProductIdentifiers:
[NSSet setWithObject: kMyFeatureIdentifier]];
request.delegate = self;
[request start];
}
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse: (SKProductsResponse *)response
{
}
- (void) request:(SKRequest *)request didFailWithError:(NSError *)error
{
NSLog(#"Failed to connect with error: %#", [error localizedDescription]);
}
output
Failed to connect with error: Cannot connect to iTunes Store
..
why not call didReceiveResponse in iphone?
You can only test purchases using a real device. Simulator will always return an error. Be sure to sign out of an iTunes store account on the device before hand and use a sandbox user.
EDIT:
See this for more guidance:
https://developer.apple.com/in-app-purchase/
Related
In my iPhone app, i've set up an in app purchase. I start the request like this:
SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObject: #"com.nicknoble.tiprounder.upgrade"]];
request.delegate = self;
[request start];
And I use this method to get the response:
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSLog(#"response recieved");
}
And this one to catch an error:
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
NSLog(#"%#",error.description);
}
But neither ever get called. My project has no warnings or errors, and I am running this on my device (iPhone). Any suggestions?
EDIT:
So it works on my window's root view controller, but not on modal view controllers i present. Here is the code from my main view controller:
TestViewController *testView = [[TestViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
[self presentModalViewController:testView animated:YES];
Here is the .h file for TestViewController:
#import <UIKit/UIKit.h>
#import <StoreKit/StoreKit.h>
#interface TestViewController : UIViewController <SKProductsRequestDelegate>
#end
Here is the code for my .m file:
#import "TestViewController.h"
#implementation TestViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObject: #"com.nicknoble.tiprounder.upgrade"]];
request.delegate = self;
[request start];
}
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
NSLog(#"%#",error.description);
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSLog(#"response recieved");
}
#end
Im desperate. Any help would be great! Thanks!
The SKProductsRequest instance need to be retained for the duration of the request. If it's not, it will die silently (without informing its delegate) because nothing else is retaining it.
Right now, ARC will get rid of your SKProductsRequest as soon as the code exits the scope where you allocate it.
A solution would be to keep your SKProductsRequest in an ivar and set it to nil when the request completes/fails. This is also handy to prevent starting a request while there's already one in progress:
// Define _productsRequest as an ivar of type SKProductsRequest in your class
- (void)someMethodThatInitiatesTheProductsRequest
{
if( _productsRequest != nil )
return; // There's already a request in progress. Don't start another one.
SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObject: #"com.nicknoble.tiprounder.upgrade"]];
request.delegate = self;
[request start];
_productsRequest = request; // <<<--- This will retain the request object
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSLog(#"response recieved");
_productsRequest = nil; // <<<--- This will release the request object
}
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
NSLog(#"%#",error.description);
_productsRequest = nil; // <<<--- This will release the request object
}
check your product identifier first.
also check if the class in which these methods are implemented is not released somehow. (might be autoreleased and got released automatically while you are waiting for response)
My app FourFourTwo Stats Zone has just went live in the App Store this evening:
I've asked a few people to test the In App Purchase and got successes on all devices except the iPhone 3G (running 4.2.1 - haven't tested with other iOS versions). I've tried debugging it on a device I have and it seems that none of the SKProductsRequest delegate methods are being called. Here's my code:
- (void)requestPurchaseOfCompetition:(Competition*)competition {
DLog("");
if ([SKPaymentQueue canMakePayments]) {
DLog(#"do store");
NSString* productIdentifier = [NSString stringWithFormat:#"%#%#_%#", kPRODUCT_IDENTIFIER_PREFIX, competition.competitionId, competition.season];
SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObject:productIdentifier]];
[[NSNotificationCenter defaultCenter] postNotificationOnMainThread:[NSNotification notificationWithName:kStoreKitHandlerNotificationRequestProductInfo object:nil userInfo:[NSDictionary dictionaryWithObject:request forKey:#"request"]]];
request.delegate = self;
[request start];
[request release];
} else {
DLog(#"no store");
// Warn the user that purchases are disabled.
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Store", #"Store") message:NSLocalizedString(#"In app purchasing is disabled for this device (in Settings > General > Restrictions). Please enable this setting to purchase more competitions.", #"In app purchasing is disabled for this device (in Settings > General > Restrictions). Please enable this setting to purchase more competitions.") delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
[alertView release];
}
}
...
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
DLog(#"response: %#", response);
DLog(#"invalid product identifiers: %#", response.invalidProductIdentifiers);
for (SKProduct *product in response.products) {
DLog(#"product: %#", product);
[[NSNotificationCenter defaultCenter] postNotificationOnMainThread:[NSNotification notificationWithName:kStoreKitHandlerNotificationGotProductInfo object:nil userInfo:[NSDictionary dictionaryWithObject:product forKey:#"product"]]];
SKPayment *payment = [SKPayment paymentWithProduct:product];
SKPaymentQueue *paymentQueue = [SKPaymentQueue defaultQueue];
[paymentQueue addTransactionObserver:self];
[paymentQueue addPayment:payment];
}
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
DLog(#"request failed: %#, %#", request, error);
[[NSNotificationCenter defaultCenter] postNotificationOnMainThread:[NSNotification notificationWithName:kStoreKitHandlerNotificationRequestProductInfoFailed object:nil userInfo:[NSDictionary dictionaryWithObject:error forKey:#"error"]]];
}
- (void)requestDidFinish:(SKRequest *)request {
DLog(#"request finished: %#", request);
}
None of the log messages in the three delegate methods are appearing.
This works fine on 3GS, iPhone 4, iPad etc but not on the iPhone 3G running 4.2.1.
Can anyone provide any insight?
You shouldn't release SKProductsRequest *request right after you start the request, but should have released it in both delegate methods. Check the documentation for parent SKRequest class which says:
When this method (requestDidFinish or request:didFailWithError:)
is called, your delegate receives no further communication from the
request and can release it.
I don't know why this worked for you in newer versions of SDK, but strictly looking at your code, request could potentially be released before the response could have invoked the delegate methods.
This is how I would do it:
- (void)requestPurchaseOfCompetition:(Competition*)competition {
DLog("");
if ([SKPaymentQueue canMakePayments]) {
DLog(#"do store");
NSString* productIdentifier = [NSString stringWithFormat:#"%#%#_%#", kPRODUCT_IDENTIFIER_PREFIX, competition.competitionId, competition.season];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObject:productIdentifier]];
[[NSNotificationCenter defaultCenter] postNotificationOnMainThread:[NSNotification notificationWithName:kStoreKitHandlerNotificationRequestProductInfo object:nil userInfo:[NSDictionary dictionaryWithObject:request forKey:#"request"]]];
request.delegate = self;
[request start];
} else {
DLog(#"no store");
// Warn the user that purchases are disabled.
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Store", #"Store") message:NSLocalizedString(#"In app purchasing is disabled for this device (in Settings > General > Restrictions). Please enable this setting to purchase more competitions.", #"In app purchasing is disabled for this device (in Settings > General > Restrictions). Please enable this setting to purchase more competitions.") delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
[alertView release];
}
}
...
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
DLog(#"response: %#", response);
DLog(#"invalid product identifiers: %#", response.invalidProductIdentifiers);
for (SKProduct *product in response.products) {
DLog(#"product: %#", product);
[[NSNotificationCenter defaultCenter] postNotificationOnMainThread:[NSNotification notificationWithName:kStoreKitHandlerNotificationGotProductInfo object:nil userInfo:[NSDictionary dictionaryWithObject:product forKey:#"product"]]];
SKPayment *payment = [SKPayment paymentWithProduct:product];
SKPaymentQueue *paymentQueue = [SKPaymentQueue defaultQueue];
[paymentQueue addTransactionObserver:self];
[paymentQueue addPayment:payment];
}
[request release];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
DLog(#"request failed: %#, %#", request, error);
[[NSNotificationCenter defaultCenter] postNotificationOnMainThread:[NSNotification notificationWithName:kStoreKitHandlerNotificationRequestProductInfoFailed object:nil userInfo:[NSDictionary dictionaryWithObject:error forKey:#"error"]]];
[request release];
}
From my ApplicationDelegate, I'm doing an NSURLConnection fetch over the network (it's wrapped in a class, as you'll see below). This one seems to work correctly: I get all the data in didReceiveData and I get the completion call connectionDidFinishLoading. At the end of connectionDidFinishLoading, I instantiate one or more of a slightly different kind of wrapper class, but they're essentially the same thing. The problem is that the second NSURLConnection's delegate is never having it's methods called.
I've looked at many different answers, but all to no avail. I'm not spawning any new threads and all the [NSThread isMainThread] checks I've littered throughout the code return true.
I'm stumped. Can anyone help me out? Here's the relevant code:
App Delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
ConnectionWrapper* w = [[ConnectionWrapper alloc] initWithParams:self
url:[NSURL URLWithString:<url>]];
[w beginFetch];
return YES;
}
...
-(void)fetchCompleted:(NSURL*)url directory:(NSString*)directory
{
NSLog(#"fetch completed");
}
-(void)fetchFailed:(NSURL*)url
{
NSLog(#"fetch failed");
}
...
ConnectionWrapper:
-(id)initWithParams:(id<ConnectionWrapperDelegate>)d url:(NSURL*)url
{
delegate = d;
connURL = url;
return [self init];
}
-(void)beginFetch
{
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:connURL];
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[conn release];
[request release];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"append");
[responseData appendData:data];
}
- (void) connectionDidFinishLoading: (NSURLConnection*) connection
{
... parsing ....
DifferentConnectionWrapper* w = [[DifferentConnectionWrapper alloc] initWithParams:self
url:[NSURL URLWithString:<different url>]];
[w beginFetch];
}
-(void)fetchCompleted:(NSURL*)URL
{
NSLog(#"completed: %#", URL);
}
-(void)fetchFailed:(NSURL*)URL
{
NSLog(#"failed");
}
DifferentConnectionWrapper:
-(id)initWithParams:(id)d url:(NSURL*)url
{
delegate = d;
connURL = url;
return [self init];
}
-(void)beginFetch
{
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:connURL];
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[conn release];
[request release];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"append");
[responseData appendData:data];
}
- (void) connectionDidFinishLoading: (NSURLConnection*) connection
{
... parsing ....
DifferentConnectionWrapper* w = [[DifferentConnectionWrapper alloc] initWithParams:self
url:[NSURL URLWithString:<different url>]];
[w beginFetch];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"got response");
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(#"got data");
[responseData appendData:data];
}
- (void) connectionDidFinishLoading: (NSURLConnection*) connection
{
NSLog(#"image saver completed: %#", connURL);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"error");
}
ConnectionWrapper and DifferentConnectionWrapper have similar functions, but there's other logic that I've omitted here for brevity.
thanks for the help. I appreciate it.
A couple of things: I don't see a didFailWithError: in your first wrapper class, and (a little off topic) are you leaking memory with your DifferentConnectionWrapper *w ?
Anyway, what I would try is: see if you can invoke DifferentConnectionWrapper directly from the appDelegate instead of the ConnectionWrapper.
And I would try to decouple the two calls in any event. When the first one finishes, and calls appDelegate, can't you launch your DifferentConnectionWrapper from there?
I realize this doesn't explain your problem, but you might get it working (and which of THOSE two things is more important, is an entirely different debate.)
I believe the problem is that you’re releasing the URL connection in -beginFetch:
-(void)beginFetch
{
NSURLRequest* request = [[NSURLRequest alloc] initWithURL:connURL];
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[conn release];
[request release];
}
The URL connection object should be kept alive and released when the connection has finished loading:
- (void) connectionDidFinishLoading: (NSURLConnection*) connection
{
... parsing ....
// *** Release the connection and whatever data you’ve kept related to
// this particular connection
[connection release];
[responseData release];
// *** or [responseData setLenght:0]; depending on how you’re
// managing responseData
DifferentConnectionWrapper* w = [[DifferentConnectionWrapper alloc] initWithParams:self
url:[NSURL URLWithString:<different url>]];
[w beginFetch];
}
or when there’s been an error:
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
// *** Release the connection and whatever data you’ve kept related to
// this particular connection
[connection release];
[responseData release];
// *** or [responseData setLenght:0]; depending on how you’re
// managing responseData
// inform the user
NSLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
Edit: Your initialiser is a tad weird:
-(id)initWithParams:(id<ConnectionWrapperDelegate>)d url:(NSURL*)url
{
delegate = d;
connURL = url;
return [self init];
}
There’s no way to know what happens unless we see the code for -init and, at any rate, this should be the designated initialiser, so it shouldn’t be sending -init to self anyway. Furthermore, you should be retaining the url object that’s being passed to the initialiser.
The following makes more sense:
-(id)initWithParams:(id<ConnectionWrapperDelegate>)d url:(NSURL*)url
{
self = [super init];
if (self) {
delegate = d;
connURL = [url retain];
}
return self;
}
Don’t forget to release the url object in -dealloc or when you’re assigning another value to connURL.
OK. It turns out that this bug was caused by something I missed. I was going into a hard spin right after that second request, which would probably screw up just about anything. Once I fixed that problem, everything worked just fine.
Thanks for the help.
Grettings to you everybody!
Here me still finding problem with implementing storekit framework.
When i run the code on my device, it sends the request but the didReceiveResponse is not getting called.am i doing wrong in my code?
Please guide me up...Thanks for any help
- (void)viewDidLoad
{
NSLog(#"View is loaded");
[self requestProductData];
if ([SKPaymentQueue canMakePayments])
{
NSLog(#"can make payments");
}
else
{
NSLog(#"cannot make payments");
}
[super viewDidLoad];
}
- (void) requestProductData
{
NSSet *productIDs = [NSSet setWithObjects:#"com.mycompany.inapppurchasetesting.productid", nil];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:productIDs];
request.delegate = self;
NSLog(#"Requesting");
[request start];
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray *myProduct = response.products;
NSArray *myInvalidProducts = response.invalidProductIdentifiers;
NSLog(#"Did recieve response");
NSLog(#"Response count is %d",response.products.count);
NSLog(#"Invalid response count is %d",response.invalidProductIdentifiers.count);
for (int i = 0; i<myProduct.count; i++)
{
NSLog(#"t:%#",[[myProduct objectAtIndex:i] localizedTitle]);
}
for(int i = 0; i < myInvalidProducts.count; i++)
{
NSLog(#"Invalid products:%#",[myInvalidProducts objectAtIndex:i]);
}
// populate UI
[request autorelease];
}
You seem to have some memory management bugs in this code, but I don't see a problem with it that would prevent the delegate from being called.
If I recall correctly, StoreKit may not fully work on the simulator. Have you tried this on the device?
Also, per this SO question, you can add a callback for errors to help diagnose your issue, like so:
- (void) request:(SKRequest *)request didFailWithError:(NSError *)error {
NSString *errorMessage = [error localizedDescription];
[self notifyUserInterfaceOfError:errorMessage];
}
I am trying to make Twitter work in my app and everything works fine except the code does not seem to recognize an error from Twitter. If the username/password are not valid, I get an error message through this function:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString* strData = [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSASCIIStringEncoding] autorelease];
NSLog(#"Received data: %#", strData ) ;
return ;
}
It prints: Received data:
Could not authenticate you.
.
However, the app continues to the post a tweet view I have and ignores the error. Obviously, I do not have something setup right to detect such an error from twitter so my question is how do I get Xcode to recognize an error like this? This uses basic http auth btw and don't mention anything about OAuth...just trying to get this to work for now.
-(void) onLogin:(id)sender
{
[loading1 startAnimating];
NSString *postURL = #"http://twitter.com/account/verify_credentials.xml";
NSMutableURLRequest *request = [ [ NSMutableURLRequest alloc ] initWithURL: [ NSURL URLWithString:postURL ] ];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:request delegate:self];
if (!theConnection)
{
UIAlertView* aler = [[UIAlertView alloc] initWithTitle:#"Network Error" message:#"Failed to Connect to twitter" delegate:nil cancelButtonTitle:#"Close" otherButtonTitles:nil];
[aler show];
[aler release];
}
else
{
receivedData = [[NSMutableData data] retain];
}
[request release];
}
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge previousFailureCount] == 0 && ![challenge proposedCredential])
{
NSURLCredential *newCredential;
newCredential=[NSURLCredential credentialWithUser:txtUsername.text
password:txtPassword.text
persistence:NSURLCredentialPersistenceNone];
[[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
}
else
{
isAuthFailed = YES;
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
Your delegate needs to implement...
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
Which should get call if the server you are trying to connect to requires authentication. Here's some example code from a program of mine...
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge previousFailureCount] == 0) {
NSURLCredential *newCredential;
newCredential=[NSURLCredential credentialWithUser:_username
password:_password
persistence:NSURLCredentialPersistenceNone];
[[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
} else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
NSLog(#"Bad Username Or Password");
}
}
}
The method - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data is called zero or more times depending on how much data is available.
In the case of a web request, it is likely that it'll be called more than once. What you need to do is create an NSMutableData object and store it as an instance variable. Then when this method is called you need to append the new data to your NSMutableData object, like so:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
//myMutableData should be an instance of NSMutableData and stored as an instance variable in your class. Don't forget to initialise it.
[myMutableData appendData:data];
}
If you don't do this, you could be missing parts of the response.
I don't know exactly how your performing the request or what kind of response you're getting back, but ideally, you will want a response formatted in XML or JSON, and you should use a parser to turn the response into something you can use.
That way, you would be able to extract error codes, messages, etc and act upon them accordingly.
Finally, if you're going to be using Twitter from your application, consider using a prebuilt library like MGTwitterEngine. It'll save you a lot of grief and hassle. Also, even though you said not to mention it, MGTwitterEngine supports OAuth now, as well, which would save you having to patch your code once the basic auth is turned off.
ok so I can now get an error response or a authentication response in xml. now, how do I say, use this response as a triggering factor for connectionDidFailWithError because if twitter send me back an error, I want to give an in-app error.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData:data];
NSXMLParser* parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:self];
[parser parse];
return;
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSMutableString *)string
{
NSLog(#"response: %#", string);
}