Is there any way to get progress from dataTaskWithURL
in swift while the data is downloading?
NSURLSession.sharedSession().dataTaskWithURL(...)
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
}
}
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
}
}
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()
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
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.