I just added background downloading to my DownloadManager class but I have an issue right now !. For example I just add a file to download , in a table view I have some values like : progress , total bytes written and etc. When I press the home button the downloading will be continue in the background and will be finished but the processing stop there ! and not continue to make process 100% , or change downloaded file size.
The download task method :
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64){
DispatchQueue.main.async(execute: {() -> Void in
let db:Double = round(Double(totalBytesExpectedToWrite) / 10000)/100
//Added by me
let dbWritten:Double = round(Double(totalBytesWritten) / 10000)/100
self.fileSize = (NSString(format:"%.2f MB of %.2f MB", dbWritten,db)) as String
self.downloadProgress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
self.downloadProgressTxt = "\(String(Int(Double(self.downloadProgress*100)))) %";
self.delegate.updateProgress()
})
}
Start download function :
func startDownload(){
let defConfigObject = URLSessionConfiguration.background(withIdentifier: "com.App.backgoundSession")
let defaultSession = URLSession(configuration: defConfigObject, delegate: self, delegateQueue: OperationQueue())
downloadTask = defaultSession.downloadTask(with: fileUrl)
downloadTask.resume()
downloadStatus = "Downloading"
isDownloading = true
delegate.updateProgress()
}
In app delegate :
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
debugPrint("handleEventsForBackgroundURLSession: \(identifier)")
completionHandler()
}
How can I update func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask method when is in the background ?
And I enabled the background mode in capabilities :
Related
In my application, I need to download a file(size:50mb), also I need to support background downloading. By the following code I can do the download even in the background. The only problem is UI is not updating properly when I tried to switch the app (say I open WhatsApp) and come back to my app without going to iPhone's Home Screen.
I debugged the code, I found didWriteData is not called.
weak var downloadTask: URLSessionDownloadTask?
lazy var downloadSession: URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier: "MYDOWNLOADIDENTIFIER")
return URLSession(configuration: configuration,
delegate: self,
delegateQueue: OperationQueue.main)
}()
Download Pressed Event:
downloadTask = self.downloadSession.downloadTask(with: fileUrl)
downloadTask?.resume()
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
//UI update for progress level
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
logger.write("urlSessionDidFinishEvents\n")
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let completionHandler = appDelegate.backgroundSessionCompletionHandler {
appDelegate.backgroundSessionCompletionHandler = nil
completionHandler()
}
}
}
AppDelegate.swift
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
backgroundSessionCompletionHandler = completionHandler
}
quick question. I want to extract the dowloaded image from the url and save it to a UI Image.
How would I do this?
fileprivate func beginDownload() {
let url = URL(string: "URL")!
let configuration = URLSessionConfiguration.default
let operationQueue = OperationQueue()
let urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: operationQueue)
let downloadTask = urlSession.downloadTask(with: url)
downloadTask.resume()
}
Here are my URL session protocol stubs:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
print(totalBytesWritten, totalBytesExpectedToWrite)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("Finished dowloading file")
}
You need to load the image from the download location created in the delegate method urlSession(_:downloadTask:didFinishDownloadingTo:):
class Request: NSObject {
func getPicture() {
let url = URL(string: "https://media.tractorsupply.com/is/image/TractorSupplyCompany/1305371?$456$")!
let session = URLSession(configuration: .default,
delegate: self,
delegateQueue: nil)
session.downloadTask(with: url).resume()
}
}
extension Request: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL) {
guard let data = try? Data(contentsOf: location),
let image = UIImage(data: data) else { return }
print(image)
}
}
Request().getPicture()
I use an SDK (YITechnology) but I have a problem to download videos that exceeds 40mb size. When I use that code in the ViewController, videos less than 40mb are okay. The problem is that if they are bigger than 40mb, it stops. How can I tell it to continue to download?
I tried that ticket but it is not exactly the same thing...
ActionCamera:
#objc public class ActionCamera : NSObject, URLSessionDownloadDelegate {
public func downloadFile(fileName: String, destFilePath: String, success: ((YICameraSDK.DownloadTask) -> ())?, fail: ((Error) -> ())?) -> YICameraSDK.ActionCamera
/// Cancel current download task.
public func cancelDownlad() -> YICameraSDK.ActionCamera
/// Download file from camera.
///
/// - Parameters:
/// - fileName: The file you want to download from camera.
/// - destFilePath: The file destination file path. Need to be a full path.
/// - success: This callback will be invoked multiple times to notify the progress.
/// - fail: This callback will be invoked if download failed.
public func downloadFile(fileName: String, destFilePath: String, success: ((YICameraSDK.DownloadTask) -> ())?, fail: ((Error) -> ())?) -> YICameraSDK.ActionCamera
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)
public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
}
ViewController.swift:
private var mCamera: ActionCamera!;
self.mCamera.downloadFile(fileName: lastFileName, destFilePath: originPath, success:
{
downloadTask in
self.testLbl.text = String(describing: downloadTask.downloadedBytes)
} , fail: {
error in
let alert = UIAlertView();
alert.message = error as? String;
alert.addButton(withTitle: "Ko")
alert.show()
});
I think it is easy for someone that is familiar with the UrlSession, but it is not my case...
I want to download a file from a certain url .
what I need is the data delegate ( the delegate which gives me the downloaded data) .
I implemented the :
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
delegate but it doesn't get called .
my code is :
func download(url: URL)
{
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
let task = session.downloadTask(with: url)
task.resume()
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
print("apending ")
self.dataaa.append(data)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
print("here")
}
the two delegate functions don't get called !
You're using the delegate methods for a data task, but you're creating a download task. Those two task types work differently and use completely different delegate methods.
I start a download in an action extension (ActionRequestHandler) like this:
private lazy var urlSession: URLSession = {
let config = URLSessionConfiguration.background(withIdentifier: "de.stefantrauth.Downloader")
config.sharedContainerIdentifier = "group.de.stefantrauth.Downloader"
return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
}()
private func initiateDownloadOfFileFrom(url: URL) {
urlSession.downloadTask(with: url).resume()
completeRequest() // this tells the system the action extension is done with its work
}
Then the download is processed by iOS in the background.
I now want to handle the finished download in my main application AppDelegate, because that is what iOS calls when the download has finished.
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
print("handleEventsForBackgroundURLSession")
urlSessionBackgroundCompletionHandler = completionHandler
}
This method gets called in background after some time as expected.
My AppDelegate also implements URLSessionDelegate and URLSessionDownloadDelegate to process updates for the download.
Especially interesting are
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
print("urlSessionDidFinishEvents")
self.urlSessionBackgroundCompletionHandler?()
self.urlSessionBackgroundCompletionHandler = nil
}
}
and
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("download finished to \(location.absoluteString)")
do {
let documentsURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let savedURL = documentsURL.appendingPathComponent(location.lastPathComponent)
try FileManager.default.moveItem(at: location, to: savedURL)
print("moved file to: \(savedURL.absoluteString)")
} catch {
print ("file error: \(error)")
}
}
Both urlSessionDidFinishEvents and didFinishDownloadingTo are not being called after handleEventsForBackgroundURLSession got called in background. Only after relaunching the app into foreground the delegate methods get called.
Why are they not getting called and what can I do to fix that?
I tried creating the URLSession in handleEventsForBackgroundURLSessionlike this:
private func initUrlSessionWith(identifier: String) {
let config = URLSessionConfiguration.background(withIdentifier: identifier)
config.sharedContainerIdentifier = "group.de.stefantrauth.Downloader"
urlSession = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
}
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
print("handleEventsForBackgroundURLSession")
initUrlSessionWith(identifier: identifier)
urlSessionBackgroundCompletionHandler = completionHandler
}
However this did not fix the problem.
Before you ask: Yes I am testing this on a real device because the simulator has problems with background task handling.
My code is actually correct. Just my way of debugging was the problem. After using console logging instead of debugging via Xcode it was working fine.
See https://forums.developer.apple.com/message/263807#263807 for more details on how to work with background url session debugging.