Подтвердить что ты не робот

Как загрузить контент, размещенный в приложении?

Я последовал за http://www.raywenderlich.com/21081/introduction-to-in-app-purchases-in-ios-6-tutorial для настройки покупки Apple в In-App. В нем перечислены продукты. Когда я хочу загрузить продукты из 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, а затем обновилDownloads вызывается Apple с downloadState == Active. Затем, Apple, Apple отменяетTransaction, даже никогда не запускать загрузку. Прогресс загрузки всегда равен 0% и обновлен. Диски никогда не вызывают с downloadState == Finished.

Я не знаю, почему моя загрузка никогда не начиналась и почему моя транзакция удаляется до завершения загрузки. У кого-нибудь есть рабочий образец?

4b9b3361

Ответ 1

Проблема в том, что я забыл явно закрыть транзакцию. Для справки мой полный код выглядит следующим образом. У него есть другие вещи, такие как отображение индикатора выполнения при загрузке, но он работает на 100%. Не беспокойтесь о Utility.h, он просто определяет некоторые макросы, такие как SAFE_RELEASE_VIEW.

По существу, я расширил образец в raywenderlich, определяя два метода покупки и загрузки.

Обратите особое внимание на updatedDownloads. После завершения загрузки я копирую содержимое в каталог пользовательских документов. Когда вы загружаетесь из Apple, каталог, который у вас есть, выглядит следующим образом:

    • ContentInfo.plist
      • Содержание
        • Ваши файлы

Apple предоставляет только путь к папке загрузки. Вы используете путь для чтения ContentInfo.plist. В моем приложении у меня есть свойство "Файлы" в ContentInfo.plist, в котором перечислены мои файлы в папке "Содержание". Затем я копирую файлы в папку "Документы". Если вы этого не сделаете, вы должны угадать, какие файлы у вас есть в папке "Содержание", или просто скопировать все внутри.

Это фактический код покупки в приложении для 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

Ответ 2

Принятие этого не является ответом на вашу конкретную проблему,

У меня возникло несколько других проблем в этой области

  • Этот метод вызывается несколько раз во время восстановления/покупки цикл, а не один раз.
  • Он также может быть вызван при перезапуске приложения без вашего вызова (для продолжения прерывания восстановления/загрузки).
  • Если вы хотите восстановить только один идентификатор продукта, вам нужно отфильтровать все остальные.
  • Вы не должны вызывать startDownloads, если загрузка состояние не ожидается/приостановлено
  • Иногда, если вы вызываете startDownloads, вы можете обратиться к ANOTHER updatedTransaction для SAME транзакции, и это download.downloadState все равно будет ждать - и если вы позвоните startDownloads второй раз, когда вы можете получить ход загрузки/завершить уведомление дважды. Это может вызвать периодические проблемы, если downloadSuccess обработчик очищает целевое местоположение перед копированием файлы. Потому что вторая загрузка на самом деле не загружается кеши, поэтому вам нечего копировать во втором уведомлении. Я работал над этим, записав локальный массив загрузок. Я знаю, что я вызвал startDownloads и полностью завершил.

Я поместил обширные отладочные операторы в обработчик хранилища и каждый аргумент case, чтобы доказать это поведение, и убедился, что я не вызываю очередь более одного раза. Я предлагаю, чтобы кто-то еще начинал делать то же самое - вставлять диагностику.

Этому не помог тот факт, что SKDownload, SKPaymentTransaction не имеют адекватных методов описания, вам придется сворачивать свои собственные.

В качестве альтернативы используйте другой обработчик хранилища на github.