0

When handling SKDownload updates I'm not sure how to react to a .failed state.

I've seen the guidance posted in Apple's Developer Forum, but that implies that I must wait until the user exits and relaunches the app to restart the download.

Later when the user relaunches the app and the addTransactionObserver method is called at launch time, the transaction observer will detect the incompleteTransaction and notify the app via the updatedTransactions delegate method. The app can again retry to download the hosted content.

Contradicting that, in this tutorial, I'm instructed to do the opposite, and finish the transaction to let the user attempt to download again by restoring the purchase.

There are a number of ways to improve the user experience even if the download failed... ...We could finish the transaction and the user can use the restore button to download the files.

If we give the user options then the transaction should not be finished until we are sure that we don’t use it anymore (e.g. if you plan to resume the download later then don’t finish the transaction).

This implies that I can resume a failed download. Is that possible?

My paymentQueue: updatedDownloads method is below:

public func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) {
    downloads.forEach ({ (download) -> Void in
        switch download.state {
        case .active:
            // Update the UI to allow user to pause/cancel download
        case .cancelled:
            // Update UI to show download cancelled
            queue.finishTransaction(download.transaction)
        case .failed:
            let description = download.error?.localizedDescription
            //  <-------------------- retry the download? What should I do here?
        case .finished:
            // Update the UI to reflect download complete
            queue.finishTransaction(download.transaction)
        case .paused:
            // Update the UI to allow user to resume download
        case .waiting:
            // Begin download immediately, on advice of: https://stackoverflow.com/a/23602553/3718806
            queue.start([download])
        }
    })
}

I expect my users to be able to reattempt a failed download, without having to relaunch the app.

How can I achieve this?

Community
  • 1
  • 1
RP-3
  • 684
  • 4
  • 22

2 Answers2

0

In case it helps anyone, what I settled on doing is:

  1. Finish the transaction since this is what Apple recommends and we don't seem to have a choice*
  2. Display an error message to the user, since there will always be one
  3. Replace the 'buy' button with a 'redownload' button. (i.e., pretend the user successfully downloaded the content and subsequently deleted it)
  4. When the 'redownload' button is pressed, attempt to purchase it again, and let storekit automatically refuse to charge the user twice and offer to let them download the content again.

Code below:

public func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) {
    downloads.forEach ({ (download) -> Void in
        switch download.state {
        ...
        case .failed:
            queue.finishTransaction(download.transaction)
            let description = download.error!.localizedDescription
            // show error to user
            // change 'buy' button text to read 'redownload'
        ...
        }
    })
}

// later, when 'redownload' is tapped to buy the same SKProduct
    let payment = SKPayment(product: product)
    SKPaymentQueue.default().add(payment)

Notes:

*since there's no way to restart a failed download (as best I can tell, someone please correct me if I'm wrong).

SKPaymentQueue.default().add(SKPayment(product: skProduct)) is safe because StoreKit will present the use with an alert reading "You've already purchased this. Would you like to get it again for free?", which is exactly what I want. When the user accepts, it will begin downloading again.

It's a clunky way to restart a download but it seems to be the most Apple-Kosher way of doing it that I can find.

RP-3
  • 684
  • 4
  • 22
-2
for download in downloads {  

    switch download.downloadState {  

    case .active:  
        // deal with active  

        break  
    case .failed:  
    // inform the user the download failed  

    // should I close the transaction here? ie:  
    SKPaymentQueue.default().finishTransaction(download.transaction)  

    break  
  // cut code...  
    }  

} // end each download  
kubrick G
  • 856
  • 6
  • 10
  • Hi Rutvik, you've just pasted the code here from the example I linked to in the question. Could you help me understand how that answers the question? – RP-3 Feb 01 '19 at 03:26