22

http://www.raywenderlich.com/21081/introduction-to-in-app-purchases-in-ios-6-tutorialに従って 、Appleがホストするアプリ内購入を設定しました。製品を一覧表示します。Appleから製品をダウンロードしたいときは、次のようにします。

-(void) paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction * transaction in transactions)
    {
        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchased:
            {
                [[SKPaymentQueue defaultQueue] startDownloads:transaction.downloads];

    ....

}

-(void) paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads
{
    NSLog(@"paymentQues");

    for (SKDownload *download in downloads)
    {
        switch (download.downloadState)
        {
            case SKDownloadStateActive:
            {
                NSLog(@"%f", download.progress); break;
             }
    ...

}

-(void) paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions
{

}

私はupdatedTransactionsでダウンロードを開始し、次にupdatedDownloadsがdownloadState==ActiveでAppleによって呼び出されます。次に、Appleは実際にダウンロードを開始せずにremoveedTransactionを呼び出します。ダウンロードの進行状況は常に0%であり、updatedDownloadsがdownloadState==Finishedで呼び出されることはありません。

ダウンロードが開始されなかった理由と、ダウンロードが完了する前にトランザクションが削除された理由がわかりません。誰かが実用的なサンプルを持っていますか?

4

2 に答える 2

35

問題は、トランザクションを明示的に閉じるのを忘れたことです。参考までに、私の完全なコードは次のとおりです。ダウンロード中にプログレスバーを表示するなど、他にもありますが、100%機能しています。Utility.hについて心配する必要はありません。これは、SAFE_RELEASE_VIEWなどのいくつかのマクロを定義するだけです。

基本的に、購入とダウンロードの2つの方法を定義することにより、raywenderlichでサンプルを拡張しました。

updatedDownloadsに細心の注意を払ってください。ダウンロードが完了したら、コンテンツをユーザーのドキュメントディレクトリにコピーします。Appleからダウンロードすると、ディレクトリは次のようになります。

    • ContentInfo.plist
      • コンテンツ
        • あなたのファイル

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
于 2012-11-05T05:30:26.837 に答える
2

これを受け入れることはあなたの特定の問題への答えではありません

私はこの分野で他のいくつかの問題を経験しました

  1. このメソッドは、復元/購入サイクル中に1回だけでなく、複数回呼び出されます。
  2. また、呼び出しなしでアプリを再起動したときに呼び出すこともできます(中断された復元/ダウンロードを続行するため)。
  3. 1つのproductIdentifierのみを復元する場合は、他のすべてを除外する必要があります。
  4. ダウンロードの状態がWaiting/Pausedでない場合は、startDownloadsを呼び出さないでください。
  5. startDownloadsを呼び出すと、同じトランザクションのupdatedTransactionへの別の呼び出しが発生する場合があります。download.downloadStateは引き続き待機中です。もう一度startDownloadsを呼び出すと、ダウンロードの進行状況/完了の通知を2回受け取ることができます。これにより、downloadSuccessハンドラーがファイルをコピーする前にターゲットの場所をクリーンアップすると、断続的な問題が発生する可能性があります。2回目のダウンロードは実際にはキャッシュにダウンロードされないため、2回目の通知でコピーするものは何もありません。私はこれを回避するために、startDownloadsと呼んでいることがわかっているダウンロードのローカル配列を記録し、完全に完了しました。

この動作を証明するために、ストアハンドラーと各caseステートメントに広範なデバッグステートメントを配置し、キューを2回以上呼び出さないようにしました。私は、始めたばかりの誰かが同じことをすることを提案します-診断を入れてください。

SKDownload、SKPaymentTransactionには適切な記述メソッドがないため、独自のメソッドを使用する必要があります。

または、githubで他の人のストアハンドラーを使用します。

于 2015-05-28T11:42:30.760 に答える