My button code below download a file from a URL, I need to link it with a Progress View to show the Downloading Progress.
#IBAction func btnStream(sender: UIButton) {
// First you need to create your audio url
if let audioUrl = NSURL(string: "http://website.com/file.mp3") {
// then lets create your document folder url
let documentsUrl = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first as! NSURL
// lets create your destination file url
let destinationUrl = documentsUrl.URLByAppendingPathComponent(audioUrl.lastPathComponent!)
println(destinationUrl)
// to check if it exists before downloading it
if NSFileManager().fileExistsAtPath(destinationUrl.path!) {
println("The file already exists at path")
// if the file doesn't exist
} else {
// just download the data from your url
if let myAudioDataFromUrl = NSData(contentsOfURL: audioUrl){
// after downloading your data you need to save it to your destination url
if myAudioDataFromUrl.writeToURL(destinationUrl, atomically: true) {
println("file saved")
} else {
println("error saving file")
}
}
}
}
}
How can i link my downloading progress with a Progress View in Swift?
Here is complete working example for you:
import UIKit
class ViewController: UIViewController, NSURLSessionDownloadDelegate {
#IBOutlet weak var progressBar: UIProgressView!
#IBOutlet weak var progressCount: UILabel!
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 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
}
let s = "http://www.qdtricks.com/wp-content/uploads/2015/02/hd-wallpapers-1080p-for-mobile.png"
let url = NSURL(string:s)!
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) {
println("downloaded \(100*writ/exp)")
taskTotalBytesWritten = Int(writ)
taskTotalBytesExpectedToWrite = Int(exp)
percentageWritten = Float(taskTotalBytesWritten) / Float(taskTotalBytesExpectedToWrite)
progressBar.progress = percentageWritten
progressCount.text = String(format: "%.01f", percentageWritten*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?) {
println("completed: error: \(error)")
}
// this is the only required NSURLSessionDownloadDelegate method
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
let documentsDirectoryURL = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first as! NSURL
println("Finished downloading!")
println(documentsDirectoryURL)
var err:NSError?
// Here you can move your downloaded file
if NSFileManager().moveItemAtURL(location, toURL: documentsDirectoryURL.URLByAppendingPathComponent(downloadTask.response!.suggestedFilename!), error: &err) {
println("File saved")
} else {
if let err = err {
println("File not saved.\n\(err.description)")
}
}
}
}
You can use NSURLSessionDownloadDelegate to achieve this whose method will be called when user downloading data.
This will show you the process into progressCount label and the progressBar will show process as count will increment. you can modify this as per your need.
You can download this example from HERE.
Check this tutorial. It's in Objective-C, but it will be easy to convert to Swift.
The principle is to implement some NSURLConnectionDataDelegatefunctions on your VC :
connection:didReceiveResponse -> You can retrieve the size of the file that will be downloaded and estimate the download percentage with it.
connection:didReceiveData -> It's the refresh function, it will be called multiple times during the download. You can compare the size of your incomplete NSData object with the size of the file.
connectionDidFinishLoading -> This method is called at the end of the download process.
Hope it helps, and don't hesitate to comment if you have some troubles converting Obj-C to Swift.
Related
I am working on a iOS app which need to download multiple file (mostly images) from server and store them in Document Directory.
I am using UrlSessionDownloadTask with background URLSession to download file using Urls.
It work fine when app is in foreground but if i go to background and comes back to foreground app freezes and crash after some time. I try many thing but nothing works.
func downloadAllImages(retryCount: Int = 0,completion: #escaping((Bool)->Void)){
var success: Bool = true
var count = 0
print("Image Downloading Start")
//SVProgressHUD.setStatus("Downloading Images...")
//CommonClass.showBanner(message: "Image Downloading Start")
for (localName, severPath) in images {
if DataManager.isInternetAvailable(){
self.dispatchGroup.enter()
self.dispatchGroupEnterCount += 1
let path = severPath
count += 1
self.DownloadFile(urlPath: path, localName: localName,itemCount: count)
}else {
success = false
CommonClass.showBanner(message: "No Internet")
}
}
dispatchGroup.notify(queue: .main) {
//retry If some Images failed to download
completion(success)
}
}
func DownloadFile(urlPath: String, localName: String, itemCount: Int){
guard let url = URL(string: urlPath) else {
DispatchQueue.main.sync {
self.leaveDispatchGroup()
}
return
}
let urlRequest = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 300)
let downloadTask = session.downloadTask(with: urlRequest)
self.localNameDict[downloadTask.taskIdentifier] = localName
taskArray.append(downloadTask)
downloadTask.resume()
}
}
extension DataManager: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
do {
let filemanager = FileManager()
let documentsURL = filemanager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent(self.localNameDict[downloadTask.taskIdentifier] ?? "img.jpeg")
if filemanager.fileExists(atPath: fileURL.path){
try? filemanager.removeItem(at: fileURL)
}
try filemanager.copyItem(at: location, to: fileURL)
}catch(let error) {
print("ERROR In Storing: \(error.localizedDescription)")
}
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let completionHandler = appDelegate.bgSessionCompletionHandler else {
return
}
appDelegate.bgSessionCompletionHandler = nil
completionHandler()
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print(error.localizedDescription)
}
self.leaveDispatchGroup()
}
func leaveDispatchGroup(){
if dispatchGroupEnterCount > 0 {
self.dispatchGroupEnterCount -= 1
self.dispatchGroup.leave()
}
}
}
running video of App:
https://youtu.be/lNkMpZRNhGY
extension URLSession {
///Background sessions let you perform uploads and downloads of content in the background while your app isn’t running. You can create a background session configuration by calling the backgroundSessionConfiguration(_:) method on the URLSessionConfiguration class.
static let background = URLSession(
configuration: .background(
withIdentifier: "foo"))
}
You have to use a session that configured for running in background.
This question already has answers here:
Observe progress of Data download in Swift?
(1 answer)
get progress from dataTaskWithURL in swift
(6 answers)
Closed 4 years ago.
I am implementing a ViewController to display a PDF previously downloaded from my server and stored locally on the device, it works correctly, but to download the PDF takes too much time and I would like to implement a progress-bar.
My code is the following, where I have tried to implement the #IBOutlet weak var downloadBar: UIProgressView! .
As I get the time it takes for the download, so I eat my code reaches 100% and the download does not end yet.
class PDFViewController: UIViewController {
#IBOutlet weak var pdfView: PDFView!
#IBOutlet weak var downloadBar: UIProgressView!
//*******
var downloader = Timer()
var minValue = 0
var maxValue = 100
//********
var namePDF:String?
override func viewDidLoad() {
super.viewDidLoad()
downloadBar.setProgress(0, animated: false)
if let pdfUrl = URL(string: "https://miserver.com/\(namePDF!).pdf") {
print(pdfUrl)
// 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(pdfUrl.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")
/************** show pdf ****************/
let pdfUrl = destinationUrl.path
let rutafile = URL(fileURLWithPath: pdfUrl)
print(pdfUrl)
if let document = PDFDocument(url: rutafile) {
pdfView.autoScales = true
pdfView.document = document
}
/************** end show pdf ****************/
// if the file doesn't exist
} else {
print("file doesn't exist")
downloader = Timer.scheduledTimer(timeInterval: 0.06, target: self, selector: (#selector(PDFViewController.updater)), userInfo: nil, repeats: true)
downloadBar.setProgress(0, animated: false)
// you can use NSURLSession.sharedSession to download the data asynchronously
URLSession.shared.downloadTask(with: pdfUrl, 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)
print("File moved to documents folder")
print("file has already been downloaded")
/************** show pdf ****************/
let pdfUrl = destinationUrl.path
let rutafile = URL(fileURLWithPath: pdfUrl)
print(pdfUrl)
if let document = PDFDocument(url: rutafile) {
self.pdfView.autoScales = true
self.pdfView.document = document
}
/************** show pdf ****************/
} catch let error as NSError {
print(error.localizedDescription)
}
}).resume()
}
}
}
#objc func updater() {
if minValue != maxValue {
minValue += 1
downloadBar.progress = Float(minValue) / Float(maxValue)
print(Float(minValue) / Float(maxValue))
} else {
minValue = 0
downloader.invalidate()
}
}
}
From already thank you very much
You can implement the URLSessionDownloadDelegate protocol. And then use the following method:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if totalBytesExpectedToWrite > 0 {
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
self.downloadBar.setProgress(progress, animated: false)
}
}
This will only update progress bar when new bytes are written. And provide an accurate estimate of your download progress. Hope this helps :)
I am downloading files in swift, and the download session is triggered by a button on each table view cell. However, I do not want the next download (if someone presses the download button on another cell) to happen until after the previous one is finished.
Is there a way that I can use something like dispatch_after to accomplish this?
Here is my code where the downloading occur, if it helps at all.
//FUNCTION TO DOWNLOAD THE PDF
//PASS THE ONLINE PDF URL AS NSURL
//ASYNC REQUEST
let defaultSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
var dataTask: NSURLSessionDataTask?
var temp_name = String()
var temp_index = Int()
var temp_indexPath = NSIndexPath()
lazy var downloadsSession: NSURLSession = {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
return session
}()
func getUrl(name: String) -> NSURL?{
let documentsUrl = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first as NSURL!
return documentsUrl.URLByAppendingPathComponent(name)
}
func getIndex() -> Int?{
return temp_index
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
if let originalURL = downloadTask.originalRequest?.URL?.absoluteString,
destinationURL = getUrl(temp_name){
let fileManager = NSFileManager.defaultManager()
do {
try fileManager.removeItemAtURL(destinationURL)
} catch {
// Non-fatal: file probably doesn't exist
}
do {
try fileManager.copyItemAtURL(location, toURL: destinationURL)
} catch let error as NSError {
print("Could not copy file to disk: \(error.localizedDescription)")
}
}
if let url = downloadTask.originalRequest?.URL?.absoluteString {
activeDownloads[url] = nil
if let trackIndex = getIndex() {
dispatch_async(dispatch_get_main_queue(), {
defaults.setBool(false, forKey: self.temp_name + "_downloading")
self.tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: trackIndex, inSection: 0)], withRowAnimation: .None)
})
}
}
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if let downloadUrl = downloadTask.originalRequest?.URL?.absoluteString,
download = activeDownloads[downloadUrl] {
download.progress = Float(totalBytesWritten)/Float(totalBytesExpectedToWrite)
if let trackIndex = getIndex(), let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: trackIndex, inSection: 0)) as? MainTableViewCell {
dispatch_async(dispatch_get_main_queue(), {
cell.progress.progress = download.progress
if(download.progress < 1.0){
cell.progress.hidden = false
}
else{
cell.progress.hidden = true
}
})
}
}
}
// Action triggered by UIButton (in this case the download button)
//Access tag, which is the IndexPath.row, using sender.tag
#IBAction func downloadFile(sender: UIButton){
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(indexPath) as! MainTableViewCell
cell.downloadButton.hidden = true
cell.progress.progress = 0
cell.progress.hidden = false
let isAvailable = true
let key = names[sender.tag] + "_offline"
defaults.setValue(isAvailable, forKey: key)
let name = (names[sender.tag])
let fileName = name + ".pdf"
let attachment = attachments[sender.tag]
temp_name = fileName
temp_index = sender.tag
temp_indexPath = indexPath
let destinationURL = getUrl(temp_name)!
defaults.setValue(destinationURL.path!, forKey: name + "_path")
defaults.synchronize()
defaults.setBool(true, forKey: name + "_downloading")
let urlString = attachment
let url = NSURL(string: urlString)
let download = PDFDownload(url: urlString)
download.downloadTask = downloadsSession.downloadTaskWithURL(url!)
download.downloadTask!.resume()
download.isDownloading = true
activeDownloads[download.url] = download
}
There is a boolean that stores whether or not a download session is occurring, so maybe there is a way that I can use that? Wait until the boolean is false to execute my code?
#Deepak kumar answer is correct,But adding dependency for each operation is not good idea.
you can do it in a simpler way. only 3 steps required.
create NSOperationQueue object.
then set the property maxConcurrentOpeations to 1.
then add operations to queue , it will perform the operations one by one.
You can use NSOperationQueue to accomplish this. Create one operationqueue and one NSOperation object to store previous operation which was added to operation queue before the current operation. on every click on tableviewcell's button create new NSOperation instance and before adding it to operationqueue do the followings.
1- check if tempoperation is nil. then assign current operation to it and then add to operation queue.
2. else add dependency on tempoperation first then assign current operation to it and then add to operation queue.
This way each task will start after the completion of previous task. Hope this will help you. :)
I am creating a new Downloader object (simply to download PDF files) and I need to call a method in my ViewController which show the message that the file is successfully downloaded. I am using the answer by Ahmet Akkök on this question and when I try to use yourOwnObject.showDownloadCompleted() it can't find the method.
ViewController:
override func viewDidLoad(){
super.viewDidLoad();
let pdfURL = "exampleToPFD.com/mypdf.pdf";
let url = NSURL(string: pdfURL);
let d = Downloader(yourOwnObject: self);
d.download(url!);
}
func showDownloadComplete(){
print("done");
}
Downloader:
import Foundation
class Downloader : NSObject, NSURLSessionDownloadDelegate {
var url : NSURL?
// will be used to do whatever is needed once download is complete
var yourOwnObject : NSObject?
var downloaded = false;
var documentDestination = "";
init(yourOwnObject : NSObject){
self.yourOwnObject = yourOwnObject
}
// is called once the download is complete
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
//copy downloaded data to your documents directory with same names as source file
let documentsUrl = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first
let destinationUrl = documentsUrl!.URLByAppendingPathComponent(url!.lastPathComponent!)
let dataFromURL = NSData(contentsOfURL: location)
dataFromURL!.writeToURL(destinationUrl, atomically: true)
// now it is time to do what is needed to be done after the download
print("download done...");
// call to the parent method here
documentDestination = destinationUrl.absoluteString;
print("DestURL" + (destinationUrl.absoluteString));
}
// method to be called to download
func download(url: NSURL) {
self.url = url
// download identifier can be customized. I used the "ulr.absoluteString"
let sessionConfig = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(url.absoluteString)
let session = NSURLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
let task = session.downloadTaskWithURL(url);
task.resume();
}
}
You can use delegate to achieve that. You can try out following
Downloader.swift
import Foundation
protocol DownloadDelegate {
func downloadCompleted()
}
class Downloader : NSObject, NSURLSessionDownloadDelegate{
var url : NSURL?
var downloadDelegate : DownloadDelegate!
// will be used to do whatever is needed once download is complete
var downloaded = false;
var documentDestination = "";
//is called once the download is complete
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL){
//copy downloaded data to your documents directory with same names as source file
let documentsUrl = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first
let destinationUrl = documentsUrl!.URLByAppendingPathComponent(url!.lastPathComponent!)
let dataFromURL = NSData(contentsOfURL: location)
dataFromURL!.writeToURL(destinationUrl, atomically: true)
//now it is time to do what is needed to be done after the download
print("download done...");
downloadDelegate. downloadCompleted()
documentDestination = destinationUrl.absoluteString;
print("DestURL" + (destinationUrl.absoluteString));
}
//this is to track progress
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64){
print((String)(totalBytesWritten)+"/"+(String)(totalBytesExpectedToWrite));
}
// if there is an error during download this will be called
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?){
if(error != nil){
//handle the error
print("Download completed with error: \(error!.localizedDescription)");
}
}
//method to be called to download
func download(url: NSURL){
self.url = url
//download identifier can be customized. I used the "ulr.absoluteString"
let sessionConfig = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(url.absoluteString)
let session = NSURLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
let task = session.downloadTaskWithURL(url);
task.resume();
}
}
ViewController :
class ViewController: UIViewController,DownloadDelegate{
override func viewDidLoad(){
super.viewDidLoad();
// Do any additional setup after loading the view, typically from a nib.
let pdfURL = "exampleToPFD.com/mypdf.pdf";
let url = NSURL(string: pdfURL);
let d = Downloader();
d.downloadDelegate = self
d.download(url!);
showToast("Download Started...");
}
func downloadCompleted() {
//download completed
}
}
I want to show progress indicator while Asynchronously downloading Image.
I have done it with single image.
But, How I will achieve this in tableView?.Because i'm using NSURLSessionDownloadDelegate and getting progress in URLSession delegate method.
But,How to perform this in tableView where cell's will be reused,different cell's have different images,all of them will have different progress values...?
Here is my code.
class ViewController: UIViewController,NSURLConnectionDataDelegate,NSURLSessionDownloadDelegate {
var session:NSURLSession?
var downloadTask:NSURLSessionDownloadTask?
var downloadURLString = "imageURL"
override func viewDidLoad() {
super.viewDidLoad()
var configuration:NSURLSessionConfiguration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("com.app.POCWhatsappLoadingIndicator")
configuration.allowsCellularAccess = true
session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
var downloadURL:NSURL = NSURL(string: downloadURLString)!
var request:NSURLRequest = NSURLRequest(URL: downloadURL)
downloadTask = self.session?.downloadTaskWithRequest(request)
downloadTask?.resume()
}
Delegate Methods
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL){
var fileManager:NSFileManager = NSFileManager.defaultManager()
var URLs:NSArray = fileManager.URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask)
var documentsDirectory:NSURL = URLs.objectAtIndex(0) as NSURL
var fromURL:NSURL = downloadTask.originalRequest.URL
var destinationURL:NSURL = documentsDirectory.URLByAppendingPathComponent(fromURL.lastPathComponent!)
var error:NSError?
fileManager.removeItemAtURL(destinationURL, error: nil)
var success:Bool = fileManager.copyItemAtURL(location, toURL: destinationURL, error: &error)
if(success){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
var image:UIImage = UIImage(contentsOfFile: destinationURL.path!)!
UIGraphicsBeginImageContext(CGSizeMake(1, 1))
var context:CGContextRef = UIGraphicsGetCurrentContext()
CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), image.CGImage)
UIGraphicsEndImageContext()
dispatch_sync(dispatch_get_main_queue(), {
self.IBDownloadedImage.image = image
})
})
}else{
println("File Copy failed: \(error)")
}
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64){
var progress:Float = ((Float(totalBytesWritten)/Float(totalBytesExpectedToWrite)))
dispatch_async(dispatch_get_main_queue(), {
self.updateProgress(progress)
})
}
Custom Methods
func updateProgress(progress:Float){
if(progress < 1.0){
self.IBProgressView.setProgress(progress, animated: true)
}
}
That works fine for Single image.
But how to do for UITableView.
Thanks.
Better use SDWebImage library with ActivityIndicator. You can get activity indicator and cache of image also.
link : sdwebimage github
You need to have your progress indicator object in UITableViewCell.
Check following answer.
Custom UITableViewCell with Progress Bar download update