5

I want to implement downloading functionality which can show completed status of downloading task with the percentage. And I'm able to do that but the problem is when the app is moving to the background and come back to the foreground at that time the delegate method didWriteData is not called in iOS12. Can anyone please help me? Here is my code

protocol DownloadDelagate {
    func downloadingProgress(value:Float)
    func downloadCompleted(identifier: Int,url: URL)
}

class DownloadManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate {

    static var shared = DownloadManager()
    var delegate: DownloadDelagate?
    var backgroundSessionCompletionHandler: (() -> Void)?

    var session : URLSession {
        get {

            let config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).background")
            config.isDiscretionary = true
            config.sessionSendsLaunchEvents = true
            return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
        }
    }

    private override init() {
    }

    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        DispatchQueue.main.async {
            if let completionHandler = self.backgroundSessionCompletionHandler {
                self.backgroundSessionCompletionHandler = nil
                completionHandler()
            }
        }
    }

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        delegate?.downloadCompleted(identifier: downloadTask.taskIdentifier, url: location)
    }

    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        if totalBytesExpectedToWrite > 0 {
            let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
            let progressPercentage = progress * 100
            delegate?.downloadingProgress(value: progressPercentage)
            print("Download with task identifier: \(downloadTask.taskIdentifier) is \(progressPercentage)% complete...")
        }
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let error = error {
            print("Task failed with error: \(error)")
        } else {
            print("Task completed successfully.")
        }
    }
}
Ankit Prajapati
  • 129
  • 1
  • 11
  • Take an object that is delegate of your DownloadManger. Add `deinit { print("class deinited") } ` in that object class. Now when you enter the background check whether your delegate object is still alive. You'll see printed statement if it was deinitialized. That basically means that your weak reference is freed and your function is not firing because it has no delegate. – inokey Nov 23 '18 at 09:46
  • are you telling about this method `func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)` as `didWriteData` ? – Vicky_Vignesh Jul 18 '19 at 06:58
  • @Vicky_Vignesh Yes, this method. – Nick Jul 18 '19 at 10:34

2 Answers2

5

Based on this thread this is a bug in NSURLSesstion. Currently there are known workaround for this (approved by Apple Engineers):

var session: URLSession?
...
func applicationDidBecomeActive(_ application: UIApplication) {
    session?.getAllTasks { tasks in
        tasks.first?.resume() // It is enough to call resume() on only one task
        // If it didn't work, you can try to resume all
        // tasks.forEach { $0.resume() }
    }
}
Sergey Kuryanov
  • 6,114
  • 30
  • 52
0

Please try your code in AppDelegate's applicationWillEnterForeground(). You can make changes here when the app makes transition from Background to Active state.

Bella
  • 88
  • 1
  • 15