swift iOS - NSURLSession not downloading multiple files properly - ios

My App downloads multiple files at the same time. My scenario is as follows:
The user clicks a download button.
A Download UITableViewCell is initialized that implements NSURLSessionDelegate to download the specify file.
A download is started. The Download Cell shows progress as the file is being downloaded using the provided Delegates by NSURLSession.
The file is saved locally in the documents folder.
The user clicks a button the clears all the finished downloads. This button clears the array 'downloadQueue' that Download Cell uses. The button also calls tableView.reloadData to update the tableView
The problem is, when the user initiates multiple downloads, no problem occurs. All files download properly simultaneously. However when the user clicks the button described in step 5, and then tries to download another file, the download progress shows 100% completed immediately. And no file is saved locally. This is probably due to didFinishDownloadingToURL being called immediately.
Here is the complete code of my DownloadCell.swift
class DownloadCell: UITableViewCell, NSURLSessionDelegate {
#IBOutlet weak var lblSongName: UILabel!
#IBOutlet weak var progressBar: UIProgressView!
#IBOutlet weak var progressCount: UILabel!
var song: Song!
var task: NSURLSessionTask!
var percentageWritten: Float = 0.0
var taskTotalBytesWritten = 0
var taskTotalBytesExpectedToWrite = 0
lazy var session: NSURLSession = {
let config = NSURLSessionConfiguration.ephemeralSessionConfiguration()
config.allowsCellularAccess = false
let session = NSURLSession(configuration: config, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
return session
}()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func startDownload() {
lblSongName.text = song.songName
if self.task != nil {
return
}
let url = NSURL(string: song.songDownloadLink)!
let req = NSMutableURLRequest(URL: url)
let task = self.session.downloadTaskWithRequest(req)
self.task = task
task.resume()
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten writ: Int64, totalBytesExpectedToWrite exp: Int64) {
taskTotalBytesWritten = Int(writ)
taskTotalBytesExpectedToWrite = Int(exp)
percentageWritten = Float(taskTotalBytesWritten) / Float(taskTotalBytesExpectedToWrite)
progressBar.progress = percentageWritten
progressCount.text = String(Int(percentageWritten*100)) + "%"
print("(\(taskTotalBytesWritten) / \(taskTotalBytesExpectedToWrite))")
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if error != nil {
print("Completed with error: \(error)")
}
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
do {
let documentsDirectoryURL = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first as NSURL!
progressCount.text = "Done!"
progressBar.progress = 100.0
try NSFileManager().moveItemAtURL(location, toURL: documentsDirectoryURL.URLByAppendingPathComponent(song.songName + ".mp3"))
//Handling Badge Values
let tabBar = UIApplication.sharedApplication().keyWindow?.rootViewController as! UITabBarController
let downloadVC = tabBar.viewControllers?[1] as! DownloadViewController
let badgeValue = Int(downloadVC.tabBarItem.badgeValue!)! - 1
downloadVC.tabBarItem.badgeValue = String(badgeValue)
if badgeValue == 0 {
downloadVC.tabBarItem.badgeValue = nil
}
} catch {
print("Error occurred during saving.")
progressCount.text = String(error)
}
}
}

Maybe you should implement the prepareForReuse func of uitableviewcell? If these cells are being reused, they still might have a task, so startDownload returns at the beginning. Also the progressbar's value is not resetted to 0.

Related

How to show Progress in swift - iOS [duplicate]

Is there any way to get progress from dataTaskWithURL in swift while the data is downloading?
NSURLSession.sharedSession().dataTaskWithURL(...)
I need to show progress bar while the data is downloading.
You can simply observe progress property of the URLSessionDataTask object.
Example:
import UIKit
class SomeViewController: UIViewController {
private var observation: NSKeyValueObservation?
deinit {
observation?.invalidate()
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://source.unsplash.com/random/4000x4000")!
let task = URLSession.shared.dataTask(with: url)
observation = task.progress.observe(\.fractionCompleted) { progress, _ in
print("progress: ", progress.fractionCompleted)
}
task.resume()
}
}
Playground example:
import Foundation
import PlaygroundSupport
let page = PlaygroundPage.current
page.needsIndefiniteExecution = true
let url = URL(string: "https://source.unsplash.com/random/4000x4000")!
let task = URLSession.shared.dataTask(with: url) { _, _, _ in
page.finishExecution()
}
// Don't forget to invalidate the observation when you don't need it anymore.
let observation = task.progress.observe(\.fractionCompleted) { progress, _ in
print(progress.fractionCompleted)
}
task.resume()
you can use this code for showing download process with progress bar with its delegate functions.
import UIKit
class ViewController: UIViewController,NSURLSessionDelegate,NSURLSessionDataDelegate{
#IBOutlet weak var progress: UIProgressView!
var buffer:NSMutableData = NSMutableData()
var session:NSURLSession?
var dataTask:NSURLSessionDataTask?
let url = NSURL(string:"http://i.stack.imgur.com/b8zkg.png" )!
var expectedContentLength = 0
override func viewDidLoad() {
super.viewDidLoad()
progress.progress = 0.0
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let manqueue = NSOperationQueue.mainQueue()
session = NSURLSession(configuration: configuration, delegate:self, delegateQueue: manqueue)
dataTask = session?.dataTaskWithRequest(NSURLRequest(URL: url))
dataTask?.resume()
// Do any additional setup after loading the view, typically from a nib.
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
//here you can get full lenth of your content
expectedContentLength = Int(response.expectedContentLength)
println(expectedContentLength)
completionHandler(NSURLSessionResponseDisposition.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
buffer.appendData(data)
let percentageDownloaded = Float(buffer.length) / Float(expectedContentLength)
progress.progress = percentageDownloaded
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
//use buffer here.Download is done
progress.progress = 1.0 // download 100% complete
}
}
Update for Swift4:
Supports execution of multiple simultaneous operations.
File: DownloadService.swift. Keeps reference to URLSession and tracks executing tasks.
final class DownloadService: NSObject {
private var session: URLSession!
private var downloadTasks = [GenericDownloadTask]()
public static let shared = DownloadService()
private override init() {
super.init()
let configuration = URLSessionConfiguration.default
session = URLSession(configuration: configuration,
delegate: self, delegateQueue: nil)
}
func download(request: URLRequest) -> DownloadTask {
let task = session.dataTask(with: request)
let downloadTask = GenericDownloadTask(task: task)
downloadTasks.append(downloadTask)
return downloadTask
}
}
extension DownloadService: URLSessionDataDelegate {
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
guard let task = downloadTasks.first(where: { $0.task == dataTask }) else {
completionHandler(.cancel)
return
}
task.expectedContentLength = response.expectedContentLength
completionHandler(.allow)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
guard let task = downloadTasks.first(where: { $0.task == dataTask }) else {
return
}
task.buffer.append(data)
let percentageDownloaded = Double(task.buffer.count) / Double(task.expectedContentLength)
DispatchQueue.main.async {
task.progressHandler?(percentageDownloaded)
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let index = downloadTasks.index(where: { $0.task == task }) else {
return
}
let task = downloadTasks.remove(at: index)
DispatchQueue.main.async {
if let e = error {
task.completionHandler?(.failure(e))
} else {
task.completionHandler?(.success(task.buffer))
}
}
}
}
File: DownloadTask.swift. Lightweight interface just to hide concrete implementation.
protocol DownloadTask {
var completionHandler: ResultType<Data>.Completion? { get set }
var progressHandler: ((Double) -> Void)? { get set }
func resume()
func suspend()
func cancel()
}
File: GenericDownloadTask.swift. Concrete implementation of DownloadTask interface.
class GenericDownloadTask {
var completionHandler: ResultType<Data>.Completion?
var progressHandler: ((Double) -> Void)?
private(set) var task: URLSessionDataTask
var expectedContentLength: Int64 = 0
var buffer = Data()
init(task: URLSessionDataTask) {
self.task = task
}
deinit {
print("Deinit: \(task.originalRequest?.url?.absoluteString ?? "")")
}
}
extension GenericDownloadTask: DownloadTask {
func resume() {
task.resume()
}
func suspend() {
task.suspend()
}
func cancel() {
task.cancel()
}
}
File: ResultType.swift. Reusable type to keep result or error.
public enum ResultType<T> {
public typealias Completion = (ResultType<T>) -> Void
case success(T)
case failure(Swift.Error)
}
Usage: Example how to run two download tasks in parallel (macOS App):
class ViewController: NSViewController {
#IBOutlet fileprivate weak var loadImageButton1: NSButton!
#IBOutlet fileprivate weak var loadProgressIndicator1: NSProgressIndicator!
#IBOutlet fileprivate weak var imageView1: NSImageView!
#IBOutlet fileprivate weak var loadImageButton2: NSButton!
#IBOutlet fileprivate weak var loadProgressIndicator2: NSProgressIndicator!
#IBOutlet fileprivate weak var imageView2: NSImageView!
fileprivate var downloadTask1: DownloadTask?
fileprivate var downloadTask2: DownloadTask?
override func viewDidLoad() {
super.viewDidLoad()
loadImageButton1.target = self
loadImageButton1.action = #selector(startDownload1(_:))
loadImageButton2.target = self
loadImageButton2.action = #selector(startDownload2(_:))
}
}
extension ViewController {
#objc fileprivate func startDownload1(_ button: NSButton) {
let url = URL(string: "http://localhost:8001/?imageID=01&tilestamp=\(Date.timeIntervalSinceReferenceDate)")!
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
downloadTask1 = DownloadService.shared.download(request: request)
downloadTask1?.completionHandler = { [weak self] in
switch $0 {
case .failure(let error):
print(error)
case .success(let data):
print("Number of bytes: \(data.count)")
self?.imageView1.image = NSImage(data: data)
}
self?.downloadTask1 = nil
self?.loadImageButton1.isEnabled = true
}
downloadTask1?.progressHandler = { [weak self] in
print("Task1: \($0)")
self?.loadProgressIndicator1.doubleValue = $0
}
loadImageButton1.isEnabled = false
imageView1.image = nil
loadProgressIndicator1.doubleValue = 0
downloadTask1?.resume()
}
#objc fileprivate func startDownload2(_ button: NSButton) {
let url = URL(string: "http://localhost:8002/?imageID=02&tilestamp=\(Date.timeIntervalSinceReferenceDate)")!
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
downloadTask2 = DownloadService.shared.download(request: request)
downloadTask2?.completionHandler = { [weak self] in
switch $0 {
case .failure(let error):
print(error)
case .success(let data):
print("Number of bytes: \(data.count)")
self?.imageView2.image = NSImage(data: data)
}
self?.downloadTask2 = nil
self?.loadImageButton2.isEnabled = true
}
downloadTask2?.progressHandler = { [weak self] in
print("Task2: \($0)")
self?.loadProgressIndicator2.doubleValue = $0
}
loadImageButton2.isEnabled = false
imageView2.image = nil
loadProgressIndicator2.doubleValue = 0
downloadTask2?.resume()
}
}
Bonus 1. File StartPHPWebServer.command. Example script to run 2 Build-in PHP servers to simulate simultaneous downloads.
#!/bin/bash
AWLScriptDirPath=$(cd "$(dirname "$0")"; pwd)
cd "$AWLScriptDirPath"
php -S localhost:8001 &
php -S localhost:8002 &
ps -afx | grep php
echo "Press ENTER to exit."
read
killall php
Bonus 2. File index.php. Example PHP script to implement slow download.
<?php
$imageID = $_REQUEST["imageID"];
$local_file = "Image-$imageID.jpg";
$download_rate = 20.5; // set the download rate limit (=> 20,5 kb/s)
if (file_exists($local_file) && is_file($local_file)) {
header('Cache-control: private');
header('Content-Type: image/jpeg');
header('Content-Length: '.filesize($local_file));
flush();
$file = fopen($local_file, "r");
while(!feof($file)) {
// send the current file part to the browser
print fread($file, round($download_rate * 1024));
flush(); // flush the content to the browser
usleep(0.25 * 1000000);
}
fclose($file);}
else {
die('Error: The file '.$local_file.' does not exist!');
}
?>
Misc: Contents of directory which simulates 2 PHP servers.
Image-01.jpg
Image-02.jpg
StartPHPWebServer.command
index.php
in class declare
class AudioPlayerViewController: UIViewController, URLSessionDelegate, URLSessionDataDelegate, URLSessionDownloadDelegate{
var defaultSession: URLSession!
var downloadTask: URLSessionDownloadTask!
in download method
defaultSession = Foundation.URLSession(configuration: .default, delegate: self, delegateQueue: nil)
downloadProgress.setProgress(0.0, animated: false)
downloadTask = defaultSession.downloadTask(with: audioUrl)
downloadTask.Resume()
And add following delegates:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
DispatchQueue.main.async {
self.downloadProgressBar.setProgress(Float(totalBytesWritten)/Float(totalBytesExpectedToWrite), animated: true)
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
DispatchQueue.main.async {
//DOWNLOAD SUCCESSFUL AND FILE PATH WILL BE IN URL.
}
}
In reference to Dharmesh's work, there have been quite a number of minor changes with URLSessionDelegate and URLSessionDataDelegate delegate methods. Here come the code for Swift 4.2 compatibility.
import UIKit
class ViewController: UIViewController, URLSessionDelegate, URLSessionDataDelegate {
// MARK: - Variables
// MARK: - IBOutlet
#IBOutlet weak var progress: UIProgressView!
// MARK: - IBAction
#IBAction func goTapped(_ sender: UIButton) {
let url = URL(string: "http://www.example.com/file.zip")!
fetchFile(url: url)
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
func fetchFile(url: URL) {
progress.progress = 0.0
let configuration = URLSessionConfiguration.default
let mainQueue = OperationQueue.main
session = URLSession(configuration: configuration, delegate: self, delegateQueue: mainQueue)
dataTask = session?.dataTask(with: URLRequest(url: url))
dataTask?.resume()
}
var buffer: NSMutableData = NSMutableData()
var session: URLSession?
var dataTask: URLSessionDataTask?
var expectedContentLength = 0
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
buffer.append(data)
let percentageDownloaded = Float(buffer.length) / Float(expectedContentLength)
progress.progress = percentageDownloaded
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: (URLSession.ResponseDisposition) -> Void) {
expectedContentLength = Int(response.expectedContentLength)
completionHandler(URLSession.ResponseDisposition.allow)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
progress.progress = 1.0
}
}
for the data been download you need to set the NSURLSessionDownloadDelegate and to implement URLSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)
There is a nice tutorial about it here but is in object-c.

how to still show the progress of progressView after reloading the view controller?

this is my first question on swift, i couldn't really find any answer in the internet. i have a download button that download a pdf file from url, and there is a progressView that shows the progress of downloading to the user. everything works fine but when i download the file and reload the view controller the progress of progressView is resets to zero. how can i still show the progress of downloading even after reloading the viewController.
I'm passing the url from another viewController
please help me.
this is my code
class CellDetailsViewController: UIViewController , URLSessionDownloadDelegate{
var download = ""
var downloadTask : URLSessionDownloadTask!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func download(_ sender: Any) {
guard let url = URL(string: download)else {return}
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
downloadTask = urlSession.downloadTask(with: url)
downloadTask.resume()
print("downloading")
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("download Location", location)
let fileManager = FileManager.default
let documentPath = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let PdfName = documentPath.appendingPathComponent("\(name).pdf")
try? fileManager.removeItem(at: PdfName)
do{
try fileManager.moveItem(at: location, to: PdfName)
}catch{
print("copy error\(error.localizedDescription)")
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if totalBytesExpectedToWrite > 0 {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
DispatchQueue.main.async {
self.progressView.progress = progress
print(totalBytesExpectedToWrite)
if progress == 1 {
self.progressView.isHidden = true
}
}
}
}
}
You can store a static variable in a struct somewhere else. I would create a new file that you can store in a "Model" folder and do something like this:
struct staticVariableStorage {
static var fileProgress : Double = 0.0 }
and then you could access it in your code by setting a new value to
staticVariableStorage.fileProgress
This shouldn't be affected by resetting the ViewController seeing as it would be separate from the ViewController in the first place.

swift 2 NSURLSessionDownloadTask completes even when there are still files left to be downloaded

I am trying to download 30+ images using NSURLSessionDownloadTask, the problem is that it won't finish downloading everything. Sometimes, only 26 images are download but other times it is more or less. I've checked my url's and all of them exist but I don't understand why it's doing this. I have tried downloading using the json urls directly from the server and it crashes because of an empty optional which is caused I think by a connection timeout. I understand that happens because the internet here is not that stable. But when I tried to download the whole json file and saved it in my document directory and tried to download using the json that is saved locally, it doesn't crash anymore since I won't need to get the urls from the server. However, what happens is that my progress bar completes but not all images are downloaded. It's like the other urls are being ignored.
import UIKit
class ViewController: UIViewController, NSURLSessionDownloadDelegate {
var taskProgress = [NSURL: (written: Int64, expected: Int64)]()
#IBOutlet weak var progressBar: UIProgressView!
#IBOutlet weak var progressCount: UILabel!
var images = [String]()
var task : NSURLSessionTask!
var pics = ["http://pillar.foundationu.com/wp-content/uploads/2016/03/banner.jpg", "http://pillar.foundationu.com/wp-content/uploads/2016/03/Abad-Edmerl.jpg", "http://pillar.foundationu.com/wp-content/uploads/2016/03/Abellana-Deniz-Dawn-1.jpg", "http://pillar.foundationu.com/wp-content/uploads/2016/03/Abequibel-Arneth.jpg", "http://pillar.foundationu.com/wp-content/uploads/2016/03/Abilla-Harriette.jpg", "http://pillar.foundationu.com/wp-content/uploads/2016/03/Pasko-Maria-Katrina.jpg", "http://pillar.foundationu.com/wp-content/uploads/2016/03/Pastias-Grace.jpg"]
var counter:Float = 0.0 {
didSet {
let fractionalProgress = Float(counter) / 100.0
let animated = counter != 0
progressBar.setProgress(fractionalProgress, animated: animated)
progressCount.text = ("\(counter)%")
}
//The didSet is called immediately after the new value is stored. The fractionalProgress constant keeps track of the progress.
}
lazy var session : NSURLSession = {
let config = NSURLSessionConfiguration.ephemeralSessionConfiguration()
config.allowsCellularAccess = false
let session = NSURLSession(configuration: config, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
return session
}()
override func viewDidLoad() {
progressBar.setProgress(0.0, animated: true) //set progressBar to 0 at
start
}
#IBAction func doElaborateHTTP (sender:AnyObject!) {
progressCount.text = "0%"
if self.task != nil {
return
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let taskID = self.beginBackgroundUpdateTask()
let file = FileHelperClass()
var url = NSURL!()
var req = NSMutableURLRequest()
var task: NSURLSessionDownloadTask!
for s in file.parseJsonFromFile() /* fund returning images url */ {
let url = NSURL(string: s)
req = NSMutableURLRequest(URL:url!)
task = self.session.downloadTaskWithRequest(req)
self.task = task
task.resume()
}
// Do something with the result
self.endBackgroundUpdateTask(taskID)
})
}
func beginBackgroundUpdateTask() -> UIBackgroundTaskIdentifier {
return UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({})
}
func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) {
UIApplication.sharedApplication().endBackgroundTask(taskID)
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten writ: Int64, totalBytesExpectedToWrite exp: Int64) {
dispatch_async(dispatch_get_main_queue()) {
self.taskProgress[downloadTask.originalRequest!.URL!] = (writ, exp)
// Update your views, you may separate it into a different function
let totalWritten = self.taskProgress.reduce(0) { $0 + $1.1.written }
let totalExpected = self.taskProgress.reduce(0) { $0 + $1.1.expected }
let progress = Float(totalWritten) / Float(totalExpected)
self.progressBar.setProgress(progress, animated: true)
self.progressCount.text = "\(progress * 100)%"
}
}
func URLSession(session: NSURLSession, downloadTask:
NSURLSessionDownloadTask, didResumeAtOffset fileOffset: Int64,
expectedTotalBytes: Int64) {
// unused in this example
}
func URLSession(session: NSURLSession, task: NSURLSessionTask,
didCompleteWithError error: NSError?) {
// print("completed: error: \(error)")
}
// this is the only required NSURLSessionDownloadDelegate method
func URLSession(session: NSURLSession, downloadTask:
NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
// print(downloadTask.response!.suggestedFilename!)
images.append(downloadTask.response!.suggestedFilename!)
print("\n \(images)")
}
}

Get NSData after completion of NSURLSession

I am fetching data using NSURLSession but I don't know how to get the data back once the download is complete my code :
class ViewController: UIViewController,NSURLSessionDelegate,NSURLSessionDataDelegate{
#IBOutlet var imageView: UIImageView!
#IBOutlet weak var progress: UIProgressView!
var buffer:NSMutableData = NSMutableData()
var session:NSURLSession?
var dataTask:NSURLSessionDataTask?
let url = NSURL(string:"https://initiate.alphacoders.com/download/wallpaper/566046/images7/jpg/37100/1353" )!
var expectedContentLength = 0
var downloadedImage = UIImage()
var downloadedData = NSData()
override func viewDidLoad() {
super.viewDidLoad()
progress.progress = 0.0
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let manqueue = NSOperationQueue.mainQueue()
session = NSURLSession(configuration: configuration, delegate:self, delegateQueue: manqueue)
dataTask = session?.dataTaskWithRequest(NSURLRequest(URL: url))
dataTask?.resume()
// Do any additional setup after loading the view, typically from a nib.
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
//here you can get full lenth of your content
expectedContentLength = Int(response.expectedContentLength)
print(expectedContentLength)
completionHandler(NSURLSessionResponseDisposition.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
if progress.progress > 0.99 {
downloadedImage = UIImage(data: data , scale: 1)!
}
buffer.appendData(data)
print("checking")
let percentageDownloaded = Float(buffer.length) / Float(expectedContentLength)
progress.progress = percentageDownloaded
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
//use buffer here.Download is done
progress.progress = 1.0// download 100% complete
imageView.image = downloadedImage
}
}
I tried to get the data from func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData but at didReceiveData my file is not yet fully downloaded and i don't know how to get the data from didCompleteWithError function , am new in swift and still figuring out things here ,
If anybody knows here what I am missing or what should I do then please tell me, any guidance will be so appreciated and helpful.
- URLSession:downloadTask:didFinishDownloadingToURL:
Is something you are missing.
Called to inform the delegate that a download task has finished downloading.
This method gives you a file URL for the temporary file (The third parameter). Because the file is temporary, you must either open the file for reading or move it to a permanent location in your app’s sandbox container directory before returning from this delegate method.
Find more here.
EDIT 1: To check the progress override following method:
- URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:
EDIT 2: Okay I see now. Make following changes and see if it works?:
Let your class conform NSURLSessionDownloadDelegate
Change dataTask = session?.dataTaskWithRequest(NSURLRequest(URL: url)) to dataTask = session?.downloadTaskWithURL(NSURLRequest(URL: url))
That should make you download the file.

get progress from dataTaskWithURL in swift

Is there any way to get progress from dataTaskWithURL in swift while the data is downloading?
NSURLSession.sharedSession().dataTaskWithURL(...)
I need to show progress bar while the data is downloading.
You can simply observe progress property of the URLSessionDataTask object.
Example:
import UIKit
class SomeViewController: UIViewController {
private var observation: NSKeyValueObservation?
deinit {
observation?.invalidate()
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://source.unsplash.com/random/4000x4000")!
let task = URLSession.shared.dataTask(with: url)
observation = task.progress.observe(\.fractionCompleted) { progress, _ in
print("progress: ", progress.fractionCompleted)
}
task.resume()
}
}
Playground example:
import Foundation
import PlaygroundSupport
let page = PlaygroundPage.current
page.needsIndefiniteExecution = true
let url = URL(string: "https://source.unsplash.com/random/4000x4000")!
let task = URLSession.shared.dataTask(with: url) { _, _, _ in
page.finishExecution()
}
// Don't forget to invalidate the observation when you don't need it anymore.
let observation = task.progress.observe(\.fractionCompleted) { progress, _ in
print(progress.fractionCompleted)
}
task.resume()
you can use this code for showing download process with progress bar with its delegate functions.
import UIKit
class ViewController: UIViewController,NSURLSessionDelegate,NSURLSessionDataDelegate{
#IBOutlet weak var progress: UIProgressView!
var buffer:NSMutableData = NSMutableData()
var session:NSURLSession?
var dataTask:NSURLSessionDataTask?
let url = NSURL(string:"http://i.stack.imgur.com/b8zkg.png" )!
var expectedContentLength = 0
override func viewDidLoad() {
super.viewDidLoad()
progress.progress = 0.0
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let manqueue = NSOperationQueue.mainQueue()
session = NSURLSession(configuration: configuration, delegate:self, delegateQueue: manqueue)
dataTask = session?.dataTaskWithRequest(NSURLRequest(URL: url))
dataTask?.resume()
// Do any additional setup after loading the view, typically from a nib.
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
//here you can get full lenth of your content
expectedContentLength = Int(response.expectedContentLength)
println(expectedContentLength)
completionHandler(NSURLSessionResponseDisposition.Allow)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
buffer.appendData(data)
let percentageDownloaded = Float(buffer.length) / Float(expectedContentLength)
progress.progress = percentageDownloaded
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
//use buffer here.Download is done
progress.progress = 1.0 // download 100% complete
}
}
Update for Swift4:
Supports execution of multiple simultaneous operations.
File: DownloadService.swift. Keeps reference to URLSession and tracks executing tasks.
final class DownloadService: NSObject {
private var session: URLSession!
private var downloadTasks = [GenericDownloadTask]()
public static let shared = DownloadService()
private override init() {
super.init()
let configuration = URLSessionConfiguration.default
session = URLSession(configuration: configuration,
delegate: self, delegateQueue: nil)
}
func download(request: URLRequest) -> DownloadTask {
let task = session.dataTask(with: request)
let downloadTask = GenericDownloadTask(task: task)
downloadTasks.append(downloadTask)
return downloadTask
}
}
extension DownloadService: URLSessionDataDelegate {
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
guard let task = downloadTasks.first(where: { $0.task == dataTask }) else {
completionHandler(.cancel)
return
}
task.expectedContentLength = response.expectedContentLength
completionHandler(.allow)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
guard let task = downloadTasks.first(where: { $0.task == dataTask }) else {
return
}
task.buffer.append(data)
let percentageDownloaded = Double(task.buffer.count) / Double(task.expectedContentLength)
DispatchQueue.main.async {
task.progressHandler?(percentageDownloaded)
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let index = downloadTasks.index(where: { $0.task == task }) else {
return
}
let task = downloadTasks.remove(at: index)
DispatchQueue.main.async {
if let e = error {
task.completionHandler?(.failure(e))
} else {
task.completionHandler?(.success(task.buffer))
}
}
}
}
File: DownloadTask.swift. Lightweight interface just to hide concrete implementation.
protocol DownloadTask {
var completionHandler: ResultType<Data>.Completion? { get set }
var progressHandler: ((Double) -> Void)? { get set }
func resume()
func suspend()
func cancel()
}
File: GenericDownloadTask.swift. Concrete implementation of DownloadTask interface.
class GenericDownloadTask {
var completionHandler: ResultType<Data>.Completion?
var progressHandler: ((Double) -> Void)?
private(set) var task: URLSessionDataTask
var expectedContentLength: Int64 = 0
var buffer = Data()
init(task: URLSessionDataTask) {
self.task = task
}
deinit {
print("Deinit: \(task.originalRequest?.url?.absoluteString ?? "")")
}
}
extension GenericDownloadTask: DownloadTask {
func resume() {
task.resume()
}
func suspend() {
task.suspend()
}
func cancel() {
task.cancel()
}
}
File: ResultType.swift. Reusable type to keep result or error.
public enum ResultType<T> {
public typealias Completion = (ResultType<T>) -> Void
case success(T)
case failure(Swift.Error)
}
Usage: Example how to run two download tasks in parallel (macOS App):
class ViewController: NSViewController {
#IBOutlet fileprivate weak var loadImageButton1: NSButton!
#IBOutlet fileprivate weak var loadProgressIndicator1: NSProgressIndicator!
#IBOutlet fileprivate weak var imageView1: NSImageView!
#IBOutlet fileprivate weak var loadImageButton2: NSButton!
#IBOutlet fileprivate weak var loadProgressIndicator2: NSProgressIndicator!
#IBOutlet fileprivate weak var imageView2: NSImageView!
fileprivate var downloadTask1: DownloadTask?
fileprivate var downloadTask2: DownloadTask?
override func viewDidLoad() {
super.viewDidLoad()
loadImageButton1.target = self
loadImageButton1.action = #selector(startDownload1(_:))
loadImageButton2.target = self
loadImageButton2.action = #selector(startDownload2(_:))
}
}
extension ViewController {
#objc fileprivate func startDownload1(_ button: NSButton) {
let url = URL(string: "http://localhost:8001/?imageID=01&tilestamp=\(Date.timeIntervalSinceReferenceDate)")!
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
downloadTask1 = DownloadService.shared.download(request: request)
downloadTask1?.completionHandler = { [weak self] in
switch $0 {
case .failure(let error):
print(error)
case .success(let data):
print("Number of bytes: \(data.count)")
self?.imageView1.image = NSImage(data: data)
}
self?.downloadTask1 = nil
self?.loadImageButton1.isEnabled = true
}
downloadTask1?.progressHandler = { [weak self] in
print("Task1: \($0)")
self?.loadProgressIndicator1.doubleValue = $0
}
loadImageButton1.isEnabled = false
imageView1.image = nil
loadProgressIndicator1.doubleValue = 0
downloadTask1?.resume()
}
#objc fileprivate func startDownload2(_ button: NSButton) {
let url = URL(string: "http://localhost:8002/?imageID=02&tilestamp=\(Date.timeIntervalSinceReferenceDate)")!
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
downloadTask2 = DownloadService.shared.download(request: request)
downloadTask2?.completionHandler = { [weak self] in
switch $0 {
case .failure(let error):
print(error)
case .success(let data):
print("Number of bytes: \(data.count)")
self?.imageView2.image = NSImage(data: data)
}
self?.downloadTask2 = nil
self?.loadImageButton2.isEnabled = true
}
downloadTask2?.progressHandler = { [weak self] in
print("Task2: \($0)")
self?.loadProgressIndicator2.doubleValue = $0
}
loadImageButton2.isEnabled = false
imageView2.image = nil
loadProgressIndicator2.doubleValue = 0
downloadTask2?.resume()
}
}
Bonus 1. File StartPHPWebServer.command. Example script to run 2 Build-in PHP servers to simulate simultaneous downloads.
#!/bin/bash
AWLScriptDirPath=$(cd "$(dirname "$0")"; pwd)
cd "$AWLScriptDirPath"
php -S localhost:8001 &
php -S localhost:8002 &
ps -afx | grep php
echo "Press ENTER to exit."
read
killall php
Bonus 2. File index.php. Example PHP script to implement slow download.
<?php
$imageID = $_REQUEST["imageID"];
$local_file = "Image-$imageID.jpg";
$download_rate = 20.5; // set the download rate limit (=> 20,5 kb/s)
if (file_exists($local_file) && is_file($local_file)) {
header('Cache-control: private');
header('Content-Type: image/jpeg');
header('Content-Length: '.filesize($local_file));
flush();
$file = fopen($local_file, "r");
while(!feof($file)) {
// send the current file part to the browser
print fread($file, round($download_rate * 1024));
flush(); // flush the content to the browser
usleep(0.25 * 1000000);
}
fclose($file);}
else {
die('Error: The file '.$local_file.' does not exist!');
}
?>
Misc: Contents of directory which simulates 2 PHP servers.
Image-01.jpg
Image-02.jpg
StartPHPWebServer.command
index.php
in class declare
class AudioPlayerViewController: UIViewController, URLSessionDelegate, URLSessionDataDelegate, URLSessionDownloadDelegate{
var defaultSession: URLSession!
var downloadTask: URLSessionDownloadTask!
in download method
defaultSession = Foundation.URLSession(configuration: .default, delegate: self, delegateQueue: nil)
downloadProgress.setProgress(0.0, animated: false)
downloadTask = defaultSession.downloadTask(with: audioUrl)
downloadTask.Resume()
And add following delegates:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
DispatchQueue.main.async {
self.downloadProgressBar.setProgress(Float(totalBytesWritten)/Float(totalBytesExpectedToWrite), animated: true)
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
DispatchQueue.main.async {
//DOWNLOAD SUCCESSFUL AND FILE PATH WILL BE IN URL.
}
}
In reference to Dharmesh's work, there have been quite a number of minor changes with URLSessionDelegate and URLSessionDataDelegate delegate methods. Here come the code for Swift 4.2 compatibility.
import UIKit
class ViewController: UIViewController, URLSessionDelegate, URLSessionDataDelegate {
// MARK: - Variables
// MARK: - IBOutlet
#IBOutlet weak var progress: UIProgressView!
// MARK: - IBAction
#IBAction func goTapped(_ sender: UIButton) {
let url = URL(string: "http://www.example.com/file.zip")!
fetchFile(url: url)
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
func fetchFile(url: URL) {
progress.progress = 0.0
let configuration = URLSessionConfiguration.default
let mainQueue = OperationQueue.main
session = URLSession(configuration: configuration, delegate: self, delegateQueue: mainQueue)
dataTask = session?.dataTask(with: URLRequest(url: url))
dataTask?.resume()
}
var buffer: NSMutableData = NSMutableData()
var session: URLSession?
var dataTask: URLSessionDataTask?
var expectedContentLength = 0
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
buffer.append(data)
let percentageDownloaded = Float(buffer.length) / Float(expectedContentLength)
progress.progress = percentageDownloaded
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: (URLSession.ResponseDisposition) -> Void) {
expectedContentLength = Int(response.expectedContentLength)
completionHandler(URLSession.ResponseDisposition.allow)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
progress.progress = 1.0
}
}
for the data been download you need to set the NSURLSessionDownloadDelegate and to implement URLSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)
There is a nice tutorial about it here but is in object-c.

Resources