问题是我忘了明确关闭交易。作为参考,我的完整代码如下。它还有其他功能,例如在下载时显示进度条,但它是 100% 工作的。不用担心 Utility.h,它只是定义了一些宏,例如 SAFE_RELEASE_VIEW。
本质上,我通过定义购买和下载两种方法扩展了 raywenderlich 中的示例。
密切关注更新的下载。下载完成后,我将内容复制到用户的文档目录。当您从 Apple 下载时,您拥有的目录是这样的:
Apple 只为您提供下载文件夹的路径。您使用路径来读取 ContentInfo.plist。在我的应用程序中,我在 ContentInfo.plist 中有一个属性“Files”,它列出了我在 Contents 文件夹中的文件。然后我将文件复制到 Documents 文件夹。如果您不这样做,您必须猜测 Contents 文件夹中有哪些文件,或者您只需复制里面的所有文件。
这是 SmallChess (http://www.smallchess.com) 的实际应用内购买代码。
#import <StoreKit/StoreKit.h>
#import "MBProgressHUD/MBProgressHUD.h"
#import "Others/Utility.h"
#import "Store/OnlineStore.h"
NSString *const ProductPurchasedNotification = @"ProductPurchasedNotification";
@implementation StoreTransaction
@synthesize productID, payment;
@end
@interface OnlineStore () <SKProductsRequestDelegate, SKPaymentTransactionObserver, MBProgressHUDDelegate>
@end
@implementation OnlineStore
{
NSSet *_productIDs;
MBProgressHUD *_progress;
NSMutableSet * _purchasedIDs;
SKProductsRequest * _productsRequest;
RequestProductsCompletionHandler _completionHandler;
}
-(id) init
{
if ([SKPaymentQueue canMakePayments] && (self = [super init]))
{
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
#pragma mark MBProgressHUDDelegate
-(void) hudWasHidden:(MBProgressHUD *)hud
{
NSAssert(_progress, @"ddd");
[_progress removeFromSuperview];
SAFE_RELEASE_VIEW(_progress);
}
#pragma end
#pragma mark SKProductsRequestDelegate
-(void) request:(NSSet *)productIDs handler:(RequestProductsCompletionHandler)handler
{
_completionHandler = [handler copy];
_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIDs];
_productsRequest.delegate = self;
[_productsRequest start];
}
-(void) productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
_productsRequest = nil;
_completionHandler(YES, response.products);
_completionHandler = nil;
}
-(void) request:(SKRequest *)request didFailWithError:(NSError *)error
{
NSLog(@"Failed to load list of products.");
_productsRequest = nil;
_completionHandler(NO, nil);
_completionHandler = nil;
}
#pragma end
#pragma mark Transaction
-(void) provideContentForProduct:(SKPaymentTransaction *)payment productID:(NSString *)productID
{
[_purchasedIDs addObject:productID];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:productID];
[[NSUserDefaults standardUserDefaults] synchronize];
StoreTransaction *transaction = [[StoreTransaction alloc] init];
[transaction setPayment:payment];
[transaction setProductID:productID];
[[NSNotificationCenter defaultCenter] postNotificationName:ProductPurchasedNotification object:transaction userInfo:nil];
}
-(void) completeTransaction:(SKPaymentTransaction *)transaction
{
#ifdef DEBUG
NSLog(@"completeTransaction");
#endif
[self provideContentForProduct:transaction productID:transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
-(void) restoreTransaction:(SKPaymentTransaction *)transaction
{
#ifdef DEBUG
NSLog(@"restoreTransaction");
#endif
[self provideContentForProduct:transaction productID:transaction.originalTransaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
-(void) failedTransaction:(SKPaymentTransaction *)transaction
{
#ifdef DEBUG
NSLog(@"failedTransaction");
#endif
if (transaction.error.code != SKErrorPaymentCancelled)
{
NSLog(@"Transaction error: %@", transaction.error.localizedDescription);
}
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
-(void) restoreCompletedTransactions
{
#ifdef DEBUG
NSLog(@"restoreCompletedTransactions");
#endif
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
#pragma end
#pragma mark Buy & Download
-(BOOL) purchased:(NSString *)productID
{
return [_purchasedIDs containsObject:productID];
}
-(void) buy:(SKProduct *)product
{
SKPayment * payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
-(void) download:(StoreTransaction *)transaction
{
NSAssert(transaction.payment.transactionState == SKPaymentTransactionStatePurchased ||
transaction.payment.transactionState == SKPaymentTransactionStateRestored, @"The payment transaction must be completed");
if ([transaction.payment.downloads count])
{
[[SKPaymentQueue defaultQueue] startDownloads:transaction.payment.downloads];
}
}
#pragma end
#pragma mark SKPaymentTransactionObserver
-(void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
NSLog(@"RestoreCompletedTransactions");
}
-(void) paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction * transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
{
#ifdef DEBUG
NSLog(@"SKPaymentTransactionStatePurchased");
#endif
[[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];
break;
}
case SKPaymentTransactionStateFailed:
{
NSLog(@"Failed");
[self failedTransaction:transaction];
break;
}
case SKPaymentTransactionStateRestored:
{
NSLog(@"Restored");
[self restoreTransaction:transaction]; break;
}
case SKPaymentTransactionStatePurchasing:
{
#ifdef DEBUG
NSLog(@"SKPaymentTransactionStatePurchasing");
#endif
break;
}
}
}
}
-(void) paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
#ifdef DEBUG
NSLog(@"restoreCompletedTransactionsFailedWithError");
#endif
}
-(void) paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions
{
#ifdef DEBUG
NSLog(@"removedTransactions");
#endif
}
-(void) paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads
{
for (SKDownload *download in downloads)
{
switch (download.downloadState)
{
case SKDownloadStateActive:
{
#ifdef DEBUG
NSLog(@"%f", download.progress);
NSLog(@"%f remaining", download.timeRemaining);
#endif
if (download.progress == 0.0 && !_progress)
{
#define WAIT_TOO_LONG_SECONDS 60
#define TOO_LARGE_DOWNLOAD_BYTES 4194304
const BOOL instantDownload = (download.timeRemaining != SKDownloadTimeRemainingUnknown && download.timeRemaining < WAIT_TOO_LONG_SECONDS) ||
(download.contentLength < TOO_LARGE_DOWNLOAD_BYTES);
if (instantDownload)
{
UIView *window= [[UIApplication sharedApplication] keyWindow];
_progress = [[MBProgressHUD alloc] initWithView:[[UIApplication sharedApplication] keyWindow]];
[window addSubview:_progress];
[_progress show:YES];
[_progress setDelegate:self];
[_progress setDimBackground:YES];
[_progress setLabelText:@"Downloading"];
[_progress setMode:MBProgressHUDModeAnnularDeterminate];
}
else
{
NSLog(@"Implement me!");
}
}
[_progress setProgress:download.progress];
break;
}
case SKDownloadStateCancelled: { break; }
case SKDownloadStateFailed:
{
[Utility showAlert:@"Download Failed"
message:@"Failed to download. Please retry later"
cancelTitle:@"OK"
otherTitle:nil
delegate:nil];
break;
}
case SKDownloadStateFinished:
{
NSString *source = [download.contentURL relativePath];
NSDictionary *dict = [[NSMutableDictionary alloc] initWithContentsOfFile:[source stringByAppendingPathComponent:@"ContentInfo.plist"]];
if (![dict objectForKey:@"Files"])
{
[[SKPaymentQueue defaultQueue] finishTransaction:download.transaction];
return;
}
NSAssert([dict objectForKey:@"Files"], @"The Files property must be valid");
for (NSString *file in [dict objectForKey:@"Files"])
{
NSString *content = [[source stringByAppendingPathComponent:@"Contents"] stringByAppendingPathComponent:file];
NSAssert([Utility isFileExist:content], @"Content path must be valid");
// Copy the content to the Documents folder, don't bother with creating a directory for it
DEFINE_BOOL(succeed, [Utility copy:content dst:[[Utility getDocPath] stringByAppendingPathComponent:file]]);
NSAssert(succeed, @"Failed to copy the content");
#ifdef DEBUG
NSLog(@"Copied %@ to %@", content, [[Utility getDocPath] stringByAppendingPathComponent:file]);
#endif
}
if (download.transaction.transactionState == SKPaymentTransactionStatePurchased && _progress)
{
[Utility showAlert:@"Purchased Complete"
message:@"Your purchase has been completed. Please refer to the FAQ if you have any questions"
cancelTitle:@"OK"
otherTitle:nil
delegate:nil];
}
[_progress setDimBackground:NO];
[_progress hide:YES];
[[SKPaymentQueue defaultQueue] finishTransaction:download.transaction];
break;
}
case SKDownloadStatePaused:
{
#ifdef DEBUG
NSLog(@"SKDownloadStatePaused");
#endif
break;
}
case SKDownloadStateWaiting:
{
#ifdef DEBUG
NSLog(@"SKDownloadStateWaiting");
#endif
break;
}
}
}
}
#pragma end
@end