iOS UIWebView 应用内购买不工作
iOS UIWebView in-app purchase not working
我正在尝试从 UIWebView 实现应用内购买:
index.html
<html><body>
<script>
function purchase() {
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", "purchase");
document.documentElement.appendChild(iframe);
};
</script>
<br><br><br><button onclick="purchase()">Purchase!</button>
</body></html>
ViewController.m
#import "ViewController.h"
#import "PurchaseBackend.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSString* htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:NSUTF8StringEncoding error:nil];
_webview.delegate = self;
[_webview loadHTMLString:htmlString baseURL:nil];
}
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.absoluteString rangeOfString:@"purchase"].location == NSNotFound) {
return YES;
} else {
PurchaseBackend *pb = [[PurchaseBackend alloc] init];
[pb purchaseClick];
return NO;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
PurchaseBackend.h
#import <StoreKit/StoreKit.h>
@interface PurchaseBackend : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
-(void)purchaseClick;
@end
PurchaseBackend.m
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
#import "PurchaseBackend.h"
@implementation PurchaseBackend
#define productID @"com.organization.MonthSubscription"
- (void)purchaseClick{
NSLog(@"User clicked purchase button");
if([SKPaymentQueue canMakePayments]){
NSLog(@"User can make payments");
//If you have more than one in-app purchase, and would like
//to have the user purchase a different product, simply define
//another function and replace kRemoveAdsProductIdentifier with
//the identifier for the other product
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:productID]];
productsRequest.delegate = self;
[productsRequest start];
}
else{
NSLog(@"User cannot make payments due to parental controls");
//this is called the user cannot make payments, most likely 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");
//this is called if your product id is not valid, this shouldn't be called unless that happens.
}
}
- (void)purchase:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for(SKPaymentTransaction *transaction in transactions){
switch(transaction.transactionState){
case SKPaymentTransactionStatePurchasing: NSLog(@"Transaction state -> Purchasing");
//called when the user is in the process of purchasing, do not add any of your own code here.
break;
case SKPaymentTransactionStatePurchased:
//this is called when the user has successfully purchased the package (Cha-Ching!)
[self paid]; //you can add your code for what you want to happen when the user buys the purchase here
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(@"Transaction state -> Purchased");
break;
case SKPaymentTransactionStateRestored:
NSLog(@"Transaction state -> Restored");
//add the same code as you did from SKPaymentTransactionStatePurchased here
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
//called when the transaction does not finish
if(transaction.error.code == SKErrorPaymentCancelled){
NSLog(@"Transaction state -> Cancelled");
//the user cancelled the payment ;(
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)paid{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PAID"];
[[NSUserDefaults standardUserDefaults] synchronize];
NSLog(@"User Paid, state saved");
}
@end
结果
xcode 控制台
2015-12-31 13:13:26.120 AppName[392:102559] User clicked purchase button
2015-12-31 13:13:26.144 AppName[392:102559] User can make payments
(lldb)
求助!
更新:
已更新ViewController.m
#import "ViewController.h"
#import "PurchaseBackend.h"
@interface ViewController ()
@end
@implementation ViewController
PurchaseBackend *purchaseBackend;
- (void)viewDidLoad {
[super viewDidLoad];
NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSString* htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:NSUTF8StringEncoding error:nil];
_webview.delegate = self;
[_webview loadHTMLString:htmlString baseURL:nil];
}
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.absoluteString rangeOfString:@"purchase"].location == NSNotFound) {
return YES;
} else {
purchaseBackend = [[PurchaseBackend alloc] init];
[purchaseBackend purchaseClick];
return NO;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
感谢有用的评论,我发现了内存崩溃的原因 - PurchaseBackend 委托对象正在被释放,而 storekit 框架仍在使用它。我已经如上所示更新了 ViewController.m
,但现在没有崩溃,而是在打印 "User can make payments" 之后没有任何反应。我在didReceiveResponse
下了一个断点,它没有执行。
我想你可能有内存问题,"purchaseBackend" 对象似乎在它有机会捕获 didReceiveResponse 回调之前被释放。
尝试为它添加一个强 属性 并确保您的 "viewController" 没有自行释放。
为了避免此类问题,将 purchaseBackend 转换为单例可能是个好主意
您可以查看有关 ARC 的教程,以便更好地理解 iOS 中的内存管理方式:Beginning ARC in iOS 5 Tutorial。
我正在尝试从 UIWebView 实现应用内购买:
index.html
<html><body>
<script>
function purchase() {
var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", "purchase");
document.documentElement.appendChild(iframe);
};
</script>
<br><br><br><button onclick="purchase()">Purchase!</button>
</body></html>
ViewController.m
#import "ViewController.h"
#import "PurchaseBackend.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSString* htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:NSUTF8StringEncoding error:nil];
_webview.delegate = self;
[_webview loadHTMLString:htmlString baseURL:nil];
}
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.absoluteString rangeOfString:@"purchase"].location == NSNotFound) {
return YES;
} else {
PurchaseBackend *pb = [[PurchaseBackend alloc] init];
[pb purchaseClick];
return NO;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
PurchaseBackend.h
#import <StoreKit/StoreKit.h>
@interface PurchaseBackend : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
-(void)purchaseClick;
@end
PurchaseBackend.m
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
#import "PurchaseBackend.h"
@implementation PurchaseBackend
#define productID @"com.organization.MonthSubscription"
- (void)purchaseClick{
NSLog(@"User clicked purchase button");
if([SKPaymentQueue canMakePayments]){
NSLog(@"User can make payments");
//If you have more than one in-app purchase, and would like
//to have the user purchase a different product, simply define
//another function and replace kRemoveAdsProductIdentifier with
//the identifier for the other product
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:productID]];
productsRequest.delegate = self;
[productsRequest start];
}
else{
NSLog(@"User cannot make payments due to parental controls");
//this is called the user cannot make payments, most likely 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");
//this is called if your product id is not valid, this shouldn't be called unless that happens.
}
}
- (void)purchase:(SKProduct *)product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for(SKPaymentTransaction *transaction in transactions){
switch(transaction.transactionState){
case SKPaymentTransactionStatePurchasing: NSLog(@"Transaction state -> Purchasing");
//called when the user is in the process of purchasing, do not add any of your own code here.
break;
case SKPaymentTransactionStatePurchased:
//this is called when the user has successfully purchased the package (Cha-Ching!)
[self paid]; //you can add your code for what you want to happen when the user buys the purchase here
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
NSLog(@"Transaction state -> Purchased");
break;
case SKPaymentTransactionStateRestored:
NSLog(@"Transaction state -> Restored");
//add the same code as you did from SKPaymentTransactionStatePurchased here
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
//called when the transaction does not finish
if(transaction.error.code == SKErrorPaymentCancelled){
NSLog(@"Transaction state -> Cancelled");
//the user cancelled the payment ;(
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
}
}
- (void)paid{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PAID"];
[[NSUserDefaults standardUserDefaults] synchronize];
NSLog(@"User Paid, state saved");
}
@end
结果
xcode 控制台
2015-12-31 13:13:26.120 AppName[392:102559] User clicked purchase button
2015-12-31 13:13:26.144 AppName[392:102559] User can make payments
(lldb)
求助!
更新:
已更新ViewController.m
#import "ViewController.h"
#import "PurchaseBackend.h"
@interface ViewController ()
@end
@implementation ViewController
PurchaseBackend *purchaseBackend;
- (void)viewDidLoad {
[super viewDidLoad];
NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSString* htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:NSUTF8StringEncoding error:nil];
_webview.delegate = self;
[_webview loadHTMLString:htmlString baseURL:nil];
}
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.absoluteString rangeOfString:@"purchase"].location == NSNotFound) {
return YES;
} else {
purchaseBackend = [[PurchaseBackend alloc] init];
[purchaseBackend purchaseClick];
return NO;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
感谢有用的评论,我发现了内存崩溃的原因 - PurchaseBackend 委托对象正在被释放,而 storekit 框架仍在使用它。我已经如上所示更新了 ViewController.m
,但现在没有崩溃,而是在打印 "User can make payments" 之后没有任何反应。我在didReceiveResponse
下了一个断点,它没有执行。
我想你可能有内存问题,"purchaseBackend" 对象似乎在它有机会捕获 didReceiveResponse 回调之前被释放。
尝试为它添加一个强 属性 并确保您的 "viewController" 没有自行释放。
为了避免此类问题,将 purchaseBackend 转换为单例可能是个好主意
您可以查看有关 ARC 的教程,以便更好地理解 iOS 中的内存管理方式:Beginning ARC in iOS 5 Tutorial。