Как загрузить контент, размещенный в приложении?
Я следовал 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 вызывает updateDownloads с downloadState == Active. Затем Apple вызывает удаленную транзакцию, даже не начав загрузку. Ход загрузки всегда равен 0%, а updatedDownloads никогда не вызывается с параметром downloadState == Завершено.
Я не знаю, почему моя загрузка никогда не начиналась и почему моя транзакция удаляется до завершения загрузки. У кого-нибудь есть рабочий образец?
2 ответа
Проблема в том, что я забыл явно закрыть транзакцию. Для справки мой полный код выглядит следующим образом. Есть и другие вещи, такие как отображение индикатора выполнения во время загрузки, но он работает на 100%. Не беспокойтесь о Utility.h, он просто определяет некоторые макросы, такие как SAFE_RELEASE_VIEW.
По сути, я расширил образец в raywenderlich, определив два метода buy и download.
Обратите особое внимание на обновленные загрузки. После завершения загрузки я копирую содержимое в каталог документов пользователя. Когда вы загружаете из Apple, каталог, который у вас есть, выглядит так:
- ContentInfo.plist
- содержание
- Ваши файлы
- содержание
- 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
Принятие этого не является решением вашей конкретной проблемы,
Я испытал несколько других проблем в этой области
- Этот метод вызывается несколько раз в течение цикла восстановления / покупки, а не только один раз.
- Его также можно вызывать при перезапуске приложения без вашего вызова (для продолжения прерванного восстановления / загрузки).
- Если вы хотите восстановить только один productIdentifier, вы должны отфильтровать все остальные.
- Вы не должны вызывать startDownloads, если состояние загрузок не ожидает / приостановлено
- Иногда, если вы вызываете startDownloads, вы можете получить ДРУГОЙ вызов для updatedTransaction для ТО ЖЕ транзакции, и ее download.downloadState по-прежнему будет Ожидать - и если вы вызовете startDownloads во второй раз, вы можете получить уведомление о ходе / завершении загрузки дважды. Это может вызвать периодические проблемы, если ваш обработчик downloadSuccess очищает целевое местоположение перед копированием файлов. Поскольку вторая загрузка на самом деле не загружается в кэш-память, вам не нужно копировать второе уведомление. Я работал над этим, записывая локальный массив загрузок, который, как я знаю, вызвал startDownloads и полностью завершен.
Я поместил обширные операторы отладки в мой обработчик хранилища и каждый оператор case, чтобы доказать это поведение, и убедился, что я не вызываю очередь более одного раза. Я предлагаю всем, кто начинает, делает то же самое - ставит диагностику.
Это не помогает тот факт, что SKDownload, SKPaymentTransaction не имеют адекватных методов описания, вам придется свернуть свои собственные.
В качестве альтернативы можно использовать чужой обработчик магазина на github.