【问题标题】:How to download In-App hosted content?如何下载应用内托管内容?
【发布时间】:2012-10-21 03:02:28
【问题描述】:

我关注了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 中开始下载,然后Apple 使用downloadState == Active 调用updatedDownloads。然后,Apple 调用了 removedTransaction,但实际上并没有开始下载。下载进度始终为 0%,并且从不会使用 downloadState == Finished 调用 updatedDownloads。

我不知道为什么我的下载从未开始,以及为什么我的交易在下载完成之前就被删除了。有人有工作样本吗?

【问题讨论】:

  • 我有同样的问题...

标签: iphone ios


【解决方案1】:

问题是我忘了明确关闭交易。作为参考,我的完整代码如下。它还有其他功能,例如在下载时显示进度条,但它是 100% 工作的。不用担心 Utility.h,它只是定义了一些宏,例如 SAFE_RELEASE_VIEW。

基本上我通过定义购买和下载两种方法扩展了 raywenderlich 中的示例。

密切关注更新的下载。下载完成后,我将内容复制到用户的文档目录。当你从苹果下载时,你的目录是这样的:

    • 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

【讨论】:

  • 很棒的帖子。对问答都投了赞成票。但是,当您说:“问题是我忘记明确关闭交易时,显然您并没有告诉您要做什么。”我面临着下载状态永远无法完成的相同问题。虽然我现在手头没有真正的设备,但在模拟器上调试,只是变得紧张。你能放点光吗?
  • 我不能告诉你这是否会在模拟器中工作,因为我从未尝试过,但至少在早期的 iO​​S 中,这不会工作。不确定最新的 iOS。
  • 问:你的下载开始了吗? IE。它是否曾达到 0% 以外的任何值?在我的代码中,我有:NSLog(@"%f", download.progress);,请你也这样做,告诉我你有什么。
  • 超过 75% 但从未达到 100%。也从未达到 SKDownloadStateFinished 但最终失败。是的,我在 SKDownloadStateFinished 之后完成了 finishTransaction,但正如我所说,它永远不会被击中。
  • 如苹果文档中的:developer.apple.com/library/ios/#documentation/… 注意:Store Kit 可以在 iOS 模拟器中进行测试,托管内容下载除外。
【解决方案2】:

接受这个并不是你特定问题的答案

我在这方面遇到了一些其他问题

  1. 在恢复/购买过程中多次调用此方法 循环,不止一次。
  2. 也可以在应用重启时调用 没有您的调用(继续中断的恢复/下载)。
  3. 如果您只想恢复一个产品标识符,则必须过滤掉所有其他产品标识符。
  4. 你不应该调用 startDownloads 如果下载 状态不是等待/暂停
  5. 有时,如果您调用 startDownloads,您可能会收到另一个调用 相同交易的更新交易,它是 download.downloadState 仍将等待 - 如果您致电 startDownloads 第二次可以得到下载进度/完成 通知两次。这可能会导致间歇性问题,如果您 downloadSuccess 处理程序在复制之前清除目标位置 文件。因为第二次下载实际上并没有下载到 缓存,因此您无需在第二个通知中复制任何内容。我通过记录本地下载数组来解决这个问题,我知道我已经调用了 startDownloads 并完全完成。

我已将大量调试语句放入我的存储处理程序和每个 case 语句以证明这种行为,并确保我不会多次调用队列。我建议其他人开始做同样的事情 - 进行诊断。

SKDownload、SKPaymentTransaction 没有足够的描述方法,这无济于事,你必须自己动手。

或者在 github 上使用其他人的商店处理程序。

【讨论】:

  • 苹果的内购代码实现不好,大家都知道。但是您的一些观察实际上已记录在案,例如,在(1)中,我们使用它来检查当前进度。虽然这些方法没有很好地记录,但仅仅为此使用外部库有点矫枉过正。
  • 是的,如果您认为我的意思是这样,我会使用 updatedDownloads 来监控进度。但我没想到 updatedTransactions 会为相同的对象状态多次调用 - 它似乎是。我承认我可能做错了什么,但我认为调试这些例程并开发积极的测试场景(如在下载/恢复过程中退出应用程序)的总体建议是,我希望是个好建议!
  • 几年前写这篇文章时,我和你现在一样感到沮丧。我记得我坐下来调试了好几个小时。但是你会习惯的......方法 updatedTransactions 预计会被多次调用,它被命名为“更新”方法,这意味着它会在发生任何事情时被调用。
  • @GilesDMiddleton 你能澄清你在上面第四点的意思吗? Apple's StoreKitSuite sample code 也会在下载状态为 waiting 的情况下启动事务的下载:case SKDownloadStateWaiting: [[SKPaymentQueue defaultQueue] startDownloads:@[download]]; break; 你是说这是一个错误吗?
  • @Aodh 也许这是我对双重否定的错误使用。第 4 点说:只有在状态为 Waiting 或 Paused 时才调用 startDownload。
猜你喜欢
  • 2013-05-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多