2012-10-21 64 views
21

我跟着http://www.raywenderlich.com/21081/introduction-to-in-app-purchases-in-ios-6-tutorial 設立蘋果託管應用程序內購買。它列出了產品。當我想從蘋果網站下載的產品,我做這樣的事情如何下載應用內託管的內容?

-(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稱爲==活動。接下來,蘋果會調用removedTransaction,而不會真正開始下載。下載進度始終爲0%,updatedDownloads永遠不會使用downloadState ==完成調用。

我不知道爲什麼我下載從未開始,爲什麼我的交易在下載完成之前移除。任何人都有工作樣本?

+0

我有同樣的問題... –

回答

35

的問題是,我忘了明確關閉交易。供參考我的完整代碼如下。它還有其他一些功能,例如在下載時顯示進度條,但它可以100%工作。不要擔心Utility.h,它只是定義了一些宏,比如SAFE_RELEASE_VIEW。

基本上我定義兩種方法購買並下載擴展在raywenderlich樣品。

狠抓updatedDownloads。下載完成後,我將內容複製到用戶的文檔目錄。當你從蘋果網站下載,你有目錄是這樣的:

    • ContentInfo.plist
      • 內容
        • 您的文件

蘋果只給你的路徑下載文件夾。您使用該路徑來讀取ContentInfo.plist。在我的應用程序,我在ContentInfo.plist屬性「文件」,它列出了目錄文件夾我的文件。然後我將這些文件複製到Documents文件夾。如果你不這樣做,你必須猜測哪些文件在你的內容的文件夾,或者你只是拷貝里面的一切。

這是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 
+0

偉大的職位。 Upvoted這兩個問答。然而,顯然你沒有告訴你做什麼,當你說:「問題是,我忘了明確關閉交易。」我面臨同樣的下載狀態問題,從未達到完成。雖然在模擬器上進行調試,但現在我手頭沒有真正的設備,只是感到緊張。你能拋出一些光嗎? –

+0

我不能告訴你這是否會在模擬器中工作,因爲我從來沒有嘗試過,但至少在早期的iOS中,這是行不通的。不確定最新的iOS。 – SmallChess

+0

問:你的下載是否開始?即。它有沒有達到0%以外的任何東西?在我的代碼中,我有:NSLog(@「%f」,download.progress);請執行相同的操作並告訴我您的內容。 – SmallChess

2

接受這個不是回答您的具體問題

我已經經歷了在這一領域

  1. 該方法的其他一些問題,在被多次調用恢復/購買 循環,不只是一次。
  2. 它也可以在重新啓動應用程序 沒有你調用調用(繼續中斷的恢復/下載)。
  3. 如果您只想還原一個產品識別,你必須過濾掉所有其他人。
  4. 你不應該叫startDownloads如果下載 狀態不是等待/暫停
  5. 有時候,如果你打電話startDownloads,你可能會得到另一個調用 updatedTransaction出於同樣的交易,這是 download.downloadState仍然會等待 - 如果您撥打 startDownloads第二次,您可以獲得下載進度/完成 通知兩次。如果您的 downloadSuccess處理程序在複製 文件之前清理目標位置,這可能會導致間歇性問題。因爲第二次下載實際上並沒有下載到 緩存,所以你沒有什麼可以複製第二次通知。我已經通過錄制本地下載數組來解決這個問題了,我知道我已經調用了startDownloads,並且完全完成了。

我已經將廣泛的調試語句放入我的存儲處理程序和每個case語句來證明此行爲,並確保我不會多次調用該隊列。我建議其他任何人開始做同樣的事情 - 放入診斷。

SKDownload,SKPaymentTransaction沒有足夠的描述方法,你不得不推出你自己的事實。

或者在github上使用別人的存儲處理程序。

+1

蘋果的購買代碼沒有很好的實施,大家都知道。但是,實際上有一些觀察記錄,例如(1)我們用它來檢查當前的進度。雖然這些方法沒有很好的記錄,但僅僅爲它使用外部庫有點過分了。 – SmallChess

+0

是的,如果這就是您認爲我的意思,我使用updatedDownloads監控進度。但我不希望updateTransactions被多次調用相同的對象狀態 - 看起來是這樣。我接受我可能做錯了什麼,但我認爲整體建議來調試這些例程,開發積極的測試場景(比如在下載/恢復中退出應用程序),我希望得到很好的建議! – GilesDMiddleton

+0

我幾年前寫這篇文章的時候,有過和現在一樣的挫折感。我記得我坐下來調試了好幾個小時。但你會習慣它....方法updatedTransactions預計會被稱爲多次,它的名稱是一個「更新」的方法,這意味着它將在任何事情發生時被調用。 – SmallChess

相關問題