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
}
}
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 :)
Forgive if this has been asked but I've done heavy searching and can't seem to find an answer I'm looking for in swift. I have a UIWebView that loads PDF files and I wouldn't want the user to go through a tedious loading process again if they exit the WebView and come back, so how do I cache a request to make the loading quicker?
Here is my code:
var contentUrlPassedOn: String!
override func viewDidLoad() {
super.viewDidLoad()
myWebView.delegate = self
let url: NSURL! = NSURL(string: contentUrlPassedOn)
myWebView.loadRequest(NSURLRequest(URL: url))
var request: NSURLRequest
}
UPDATE
What I am trying to accomplish can be seen in this video
that is an example of dropbox's iOS app. When one of the files have been already loaded, every other trip into that file ( in this case PDF) will be quick and easy. This method is exactly what I am trying to replicate.
any suggestions?
GIF
The first time you enter this ViewController,you can see download progress
Then second time, it just load local file.So it is very quick
And code
class ViewController: UIViewController,NSURLSessionDownloadDelegate{
var sesson:NSURLSession!
var webview:UIWebView!
var progressView:UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
self.sesson = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, delegateQueue: NSOperationQueue.mainQueue())
self.webview = UIWebView(frame: self.view.frame)
self.view.addSubview(webview)
let documentDir = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first as! NSURL
let pdfFilePath = documentDir.URLByAppendingPathComponent("Test.pdf");
if NSFileManager.defaultManager().fileExistsAtPath(pdfFilePath.path!){
let request = NSURLRequest(URL: pdfFilePath)
webview.loadRequest(request)
}else{
progressView = UIProgressView(progressViewStyle: UIProgressViewStyle.Default);
progressView.frame = CGRectMake(0, 0,200, 4)
progressView.progress = 0
progressView.progressTintColor = UIColor.blueColor()
progressView.center = self.view.center
self.view.addSubview(progressView)
let remoteURL = "https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.pdf"
let request = NSURLRequest(URL: NSURL(string: remoteURL)!)
let downloadTask = self.sesson.downloadTaskWithRequest(request)
downloadTask.resume()
}
// Do any additional setup after loading the view, typically from a nib.
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
if progressView != nil{
progressView.removeFromSuperview()
}
let documentDir = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first as! NSURL
let pdfFilePath = documentDir.URLByAppendingPathComponent("Test.pdf");
NSFileManager.defaultManager().moveItemAtURL(location, toURL: pdfFilePath, error: nil)
let request = NSURLRequest(URL: pdfFilePath)
self.webview.loadRequest(request)
}
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
var curprogress = Float(totalBytesWritten)/Float(totalBytesExpectedToWrite)
progressView.progress = curprogress
}
}
NSURLConnection (and NSURLSession's shared session) uses a shared cache. You can configure the policies of that cache to use on-disk storage, but my vague recollection is that iOS uses only an in-memory cache by default. Have a look at the NSURLCache class, and do the Swift equivalent of:
NSString *myPath = ... // some subdirectory in your app's caches directory
NSURLCache *cache = [[NSURLCache alloc] initWithCapacity:2048576 /* 2M */
diskCapacity: 134217728 /* 128M */
path:myPath];
[NSURLCache setSharedURLCache:cache];
or whatever, and you'll probably have better luck.
Note that the on-disk cache in iOS is still temporary, and can be deleted by the OS, but only when your app is not running (assuming that hasn't changed recently).
Something like
let cacheDirectory = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true)[0] as String
var dir = cacheDir.stringByAppendingFormat("/urlCache/")
var cache = NSURLCache(memoryCapacity: 2048576,
diskCapacity: 134217728,
path: dir)
NSURLCache.setSharedURLCache(cache)
but I'm not all that familiar with Swift, so I could be getting the syntax wrong.
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.
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