i try to print progress of downloading using URLSessionDownloadDelegate, but delegate's methods don't work
Although image is download, progress don't print
I have button
#IBAction func downloadTapped(_ sender: UIButton) {
let image = "https://neilpatel-qvjnwj7eutn3.netdna-ssl.com/wp-content/uploads/2016/02/applelogo.jpg"
guard let url = URL(string: image) else {return}
let operationQueue = OperationQueue()
let session = URLSession(configuration: .default, delegate: self, delegateQueue: operationQueue)
session.downloadTask(with: url) { (data, response, error) in
guard let url = data else {return}
do {
let data = try Data(contentsOf: url)
OperationQueue.main.addOperation {
self.imageView.image = UIImage(data: data)
}
} catch {
}
}.resume()
}
And extension
extension DownloadingViewController: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("=====FINISH=====")
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
let progress = Float(bytesWritten) / Float(totalBytesWritten)
print(progress)
}
}
nothing at all
You are calling
session.downloadTask(with: url) { (data, response, error) in
This means that the URLSession's delegate is ignored, because the download task has a completion handler which is used instead. So what you are seeing is the expected behavior.
If you wish to use a delegate, call
session.downloadTask(with: url)
and do everything in the delegate, including receiving the downloaded file.
On the other hand, if your goal is merely to display progress, there is no need for the delegate. The download task vends a progress object for this purpose. Example:
let task = session.downloadTask(with:url) { fileURL, resp, err in
// whatever
}
// self.prog is a UIProgressView
self.prog.observedProgress = task.progress
task.resume()
Related
I write a download button that works fine, but can't show the percentage progress on the label.
I tried putting the label in the FileDownloader class, but it's not updating and showing on the interface.
0.041681744
0.045944795
0.11841663
0.12694274
0.13546883
.
.
.
99.66489
99.84393
99.84819
100.0
this is my code:
class ramdisk: NSViewController {
...
class FileDownloader : NSObject, URLSessionDownloadDelegate {
var url : URL?
// will be used to do whatever is needed once download is complete
var yourOwnObject : NSObject?
init(_ yourOwnObject : NSObject)
{
self.yourOwnObject = yourOwnObject
}
//is called once the download is complete
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)
{
//copy downloaded data to your documents directory with same names as source file
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
//FileManager.default.
let destinationUrl = documentsUrl!.appendingPathComponent(url!.lastPathComponent)
let dataFromURL = NSData(contentsOf: location)
dataFromURL?.write(to: destinationUrl, atomically: true)
//now it is time to do what is needed to be done after the download
//yourOwnObject!.callWhatIsNeeded()
}
//this is to track progress
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
let progressPercentage = progress * 100
print(progressPercentage)
}
// if there is an error during download this will be called
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)
{
if (error != nil)
{
//handle the error
print("Download completed with error: \(error!.localizedDescription)");
}
}
//method to be called to download
func download(url: URL)
{
self.url = url
//download identifier can be customized. I used the "ulr.absoluteString"
let sessionConfig = URLSessionConfiguration.background(withIdentifier: url.absoluteString)
let session = Foundation.URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
let task = session.downloadTask(with: url)
task.resume()
}
}
#IBAction func downloads(_ sender: Any) {
let strURL = "http://downloads.xyz/test.zip"
let url = URL(string: strURL)
FileDownloader(url! as NSObject).download(url: url!)
}
...
}
I write a download button that works fine, but can't show the percentage progress on the label.
I tried putting the label in the FileDownloader class, but it's not updating and showing on the interface.
I'm using my downloader class to download files through URLSession delegate method and updating progress bar with progress from the delegate method. I call the download class like this from my View:
downloader.download(url: self.video.url, fileName: self.video.filePath)
After this is complete I want to update some variables in my View to stop showing the progress bar. I was thinking of using a completion handler but I don't think this would really work whilst using the delegate for progress and completion. And I can't access the variables of my view to update in the completion delegate method of downloader.
I was wondering if it would be possible to use the delegate method for progress updates and a completion handler for completion? Is this possible?
Do you have any ideas how I could do it?
Here is my downloader class:
class download: NSObject, URLSessionDelegate, URLSessionDownloadDelegate{
#ObservedObject var globalScrollTitle: ScrollTitle = ScrollTitle.sharedInstance
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64){
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
DispatchQueue.main.async(execute: {
self.globalScrollTitle.mainprogress = CGFloat(progress)
print(self.globalScrollTitle.mainprogress)
})
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// check for and handle errors:
// * downloadTask.response should be an HTTPURLResponse with statusCode in 200..<299
print("download complete!")
do{
let downloadedData = try Data(contentsOf: location)
DispatchQueue.main.async(execute: {
print("transfer completion OK!")
let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(.downloadsDirectory, .userDomainMask, true).first! as NSString
let destinationPath = documentDirectoryPath.appendingPathComponent((downloadTask.response?.suggestedFilename)!)
let pdfFileURL = URL(fileURLWithPath: destinationPath)
FileManager.default.createFile(atPath: pdfFileURL.path,
contents: downloadedData,
attributes: nil)
if FileManager.default.fileExists(atPath: pdfFileURL.path) {
print("file present!") // Confirm that the file is here!
}
})
} catch {
print (error.localizedDescription)
}
}
func download(url: String, fileName: String) {
let myUrl = URL(string: url)
let request = URLRequest(url:myUrl!)
//let config = URLSessionConfiguration.default
//let operationQueue = OperationQueue()
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
let downloadTask = session.downloadTask(with: request)
downloadTask.resume()
//completion()
}
}
Thanks to Faysal Ahmed, I managed to find the following solution that worked. This allowed me to track my progress to update UI whilst also using a completion handler.
observation = task.progress.observe(\.fractionCompleted) { progress, _ in
DispatchQueue.main.async(execute: {
self.globalScrollTitle.mainprogress = CGFloat(progress.fractionCompleted)
})
if progress.fractionCompleted == 1 {
completion()
}
}
My download func now looks like this:
func download(url: String, fileName: String, completion: #escaping () -> Void) {
let myUrl = URL(string: url)
let request = URLRequest(url:myUrl!)
let config = URLSessionConfiguration.default
let operationQueue = OperationQueue()
let session = URLSession(configuration: config, delegate: nil, delegateQueue: operationQueue)
let task = session.dataTask(with: request) { (data, response, error) in
guard error == nil else {
print(error!)
return
}
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Success: \(statusCode)")
}
do {
let documentFolderURL = try FileManager.default.url(for: .downloadsDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let fileURL = documentFolderURL.appendingPathComponent(fileName)
try data!.write(to: fileURL)
DispatchQueue.main.async {
if FileManager.default.fileExists(atPath: fileURL.path) {
print("file present!") // Confirm that the file is here!
}
}
} catch {
print("error writing file \(error)")
}
}
observation = task.progress.observe(\.fractionCompleted) { progress, _ in
DispatchQueue.main.async(execute: {
self.globalScrollTitle.mainprogress = CGFloat(progress.fractionCompleted)
})
if progress.fractionCompleted == 1 {
completion()
}
}
task.resume()
}
}
I have implemented a background upload task using URLSession as follow:
let boundary = UUID().uuidString
let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.upload")
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: .main)
var urlRequest = URLRequest(url: URL(string: "https://server/file.php")!)
urlRequest.httpMethod = "POST"
urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
var data = Data()
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"video\"; filename=\"rawvideo.mp4\"\r\n".data(using: .utf8)!)
data.append("Content-Type: video/mp4\r\n\r\n".data(using: .utf8)!)
data.append(videoData!)
data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
let tempDir = FileManager.default.temporaryDirectory
let localURL = tempDir.appendingPathComponent("upload")
try? data.write(to: localURL)
session.uploadTask(with: urlRequest, fromFile: localURL).resume()
To handle the server response, I have used the following code [But I am not getting any print output. Seems this method is never called]
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
print("anoop")
guard let response = response as? HTTPURLResponse, (200...209).contains(response.statusCode) else {
DispatchQueue.main.async {
self.uploadComplete("Error", "Upload Server Down!")
}
print("error")
completionHandler(.cancel)
return
}
completionHandler(.allow)
}
To handle the server Data, I am using following code:
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if let toDictionary = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary {
print(toDictionary)
}
}
To handle the completion, I am using following code:
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print(error.localizedDescription)
}
}
Additionally, I have a progress bar which I am updating as follows:
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
let uploadProgress:Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
let percent = Int(uploadProgress * 100)
DispatchQueue.main.async {
self.progressLabel.text = "\(percent)% completed"
self.progressView.setProgress(Float(uploadProgress), animated: true)
}
}
This code works well if the server is working fine [Response: 200].
But if I change the request URL to a dummy url like https://server/dummyfile.php, the progress bar still increments and I don't get any error since
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void)
doesn't work.
Is it possible that in case of server errors [404, 500] the progress bar doesn't increment?
Ok, I have never saved files before (m4as in this case) and have no idea if I am doing this correctly and moreover cannot get the download progress of the URL, despite including the required URLSessionDownloadDelegate funcs here:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if totalBytesExpectedToWrite > 0 {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
print("Progress \(downloadTask) \(progress)")
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("Download finished: \(location)")
try? FileManager.default.removeItem(at: location)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("Task completed: \(task), error: \(error)")
}
I got these off a tutorial however nothing is printed to console ever. I tried calling these functions however I do not know where to get the downloadTask var or the session.
This is how I download my file, this works:
func goDownload()
{
if let audioUrl = testUrl { //set at beginning
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// lets create your destination file url
let destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
print(destinationUrl)
// to check if it exists before downloading it
if FileManager.default.fileExists(atPath: destinationUrl.path) {
print("********** The file already exists at path")
// if the file doesn't exist
} else {
// you can use NSURLSession.sharedSession to download the data asynchronously
URLSession.shared.downloadTask(with: audioUrl, completionHandler: { (location, response, error) -> Void in
guard let location = location, error == nil else { return }
do {
// after downloading your file you need to move it to your destination url
try FileManager.default.moveItem(at: location, to: destinationUrl)
//success
print("************** SUCCESS File moved to documents folder", audioUrl)
self.playModeStreaming = false
self.pausePlay()
AudioPlayerManager.shared.play(url: audioUrl)
} catch let error as NSError {
print(error.localizedDescription)
}
}).resume()
}
}
}
However even if I try to get the downloadTask from here I have type issues. How can I get the download progress (bytes received, etc) using this download func?
EDIT: This is what I have:
var session = URLSession()
let sessDelegate = CustomDelegate()
self.session = URLSession(configuration: .default, delegate: sessDelegate, delegateQueue: nil)
then replaced URLSession.shared with session in the above func.
My custom delegate class separately:
class CustomDelegate : NSObject, URLSessionDataDelegate, URLSessionTaskDelegate {
//define all `NSURLSessionDataDelegate` and `NSURLSessionTaskDelegate` methods here
//URLSessionDelegate methods
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if totalBytesExpectedToWrite > 0 {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
print("Progress \(downloadTask) \(progress)")
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("Download finished: \(location)")
try? FileManager.default.removeItem(at: location)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("Task completed: \(task), error: \(error)")
}
}
You can add a custom delegate class, and implement NSURLSessionDataDelegate and NSURLSessionTaskDelegate protocols.
class CustomDelegate : NSObject, NSURLSessionDataDelegate, NSURLSessionTaskDelegate {
//define all `NSURLSessionDataDelegate` and `NSURLSessionTaskDelegate` methods here
}
Now create an instance of above class like so :
let myCustomDelegate = CustomDelegate();
You will then need to set the delegate property of your NSURLSession instance like so :
OBJECTIVE C
NSURLSession *session=[NSURLSession sessionWithConfiguration:sessionConfig delegate:myCustomDelegate delegateQueue:nil];
SWIFT 3
let session = URLSession(configuration: .default, delegate: myCustomDelegate, delegateQueue: nil)
Ref Links:
NSURLSession Class Reference
From NSURLConnection to NSURLSession
I'm using URLSession to retrieve data from an url
func scheduleURLSession() {
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: "myIdentifier...")
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
let retrieveTask = backgroundSession.downloadTask(with: URL(string: "https://api.wedsblog.net/v2/index.php?bitcoin&simple")!)
retrieveTask.resume()
}
When download finished
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { }
Will be called. How can I read the content of downloaded file? Is ".downloadTask" the right function or shall I use another one?
Found following solution:
Implemented URLSessionDataDelegate
func scheduleURLSession() {
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: "myIdentifier")
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
let retrieveTask = backgroundSession.dataTask(with: URL(string: "https://api.wedsblog.net/v2/index.php?bitcoin&simple")!)
retrieveTask.resume()
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
print(String(data: data, encoding: String.Encoding.utf8)!)
}
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) {(data, response, error) -> Void in { // First check if error is not nil
// Second check what is status code of your http response
// Third do with data what you want(parse to objects)
}
I think it helpful.