quick question. I want to extract the dowloaded image from the url and save it to a UI Image.
How would I do this?
fileprivate func beginDownload() {
let url = URL(string: "URL")!
let configuration = URLSessionConfiguration.default
let operationQueue = OperationQueue()
let urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: operationQueue)
let downloadTask = urlSession.downloadTask(with: url)
downloadTask.resume()
}
Here are my URL session protocol stubs:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
print(totalBytesWritten, totalBytesExpectedToWrite)
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("Finished dowloading file")
}
You need to load the image from the download location created in the delegate method urlSession(_:downloadTask:didFinishDownloadingTo:):
class Request: NSObject {
func getPicture() {
let url = URL(string: "https://media.tractorsupply.com/is/image/TractorSupplyCompany/1305371?$456$")!
let session = URLSession(configuration: .default,
delegate: self,
delegateQueue: nil)
session.downloadTask(with: url).resume()
}
}
extension Request: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL) {
guard let data = try? Data(contentsOf: location),
let image = UIImage(data: data) else { return }
print(image)
}
}
Request().getPicture()
Related
Please, note that I've checked previous answers and I am not using callbacks in the code. In this case the delegate methods should be called
Environment iOS 14.4.2
URL is valid. File is downloaded. Just the delegate not get called.
The example code is below:
final class Downloader: NSObject, URLSessionDelegate {
private lazy var urlSession = URLSession(
configuration: .default,
delegate: self,
delegateQueue: .main
)
private var downloadTask: URLSessionDownloadTask?
func beginDownload(url: URL) {
downloadTask = urlSession.downloadTask(with: url)
downloadTask!.resume()
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// NEVER CALLED
}
}
The func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) method you're implementing is part of URLSessionDownloadDelegate, not URLSessionDelegate, so you should declare conformance to URLSessionDownloadDelegate as well in order for the delegate call to occur.
In my application, I need to download a file(size:50mb), also I need to support background downloading. By the following code I can do the download even in the background. The only problem is UI is not updating properly when I tried to switch the app (say I open WhatsApp) and come back to my app without going to iPhone's Home Screen.
I debugged the code, I found didWriteData is not called.
weak var downloadTask: URLSessionDownloadTask?
lazy var downloadSession: URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier: "MYDOWNLOADIDENTIFIER")
return URLSession(configuration: configuration,
delegate: self,
delegateQueue: OperationQueue.main)
}()
Download Pressed Event:
downloadTask = self.downloadSession.downloadTask(with: fileUrl)
downloadTask?.resume()
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
//UI update for progress level
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
logger.write("urlSessionDidFinishEvents\n")
DispatchQueue.main.async {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let completionHandler = appDelegate.backgroundSessionCompletionHandler {
appDelegate.backgroundSessionCompletionHandler = nil
completionHandler()
}
}
}
AppDelegate.swift
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
backgroundSessionCompletionHandler = completionHandler
}
I'm going to read file from API, but its size equal 1.6mb and it takes so much time. I wish to read it by parts, and when i founds data which i needs, i'm going to stop recieve data. I trying to use some delegate methods, but they don't works. I don't understand what goes wrong?
I have next code:
class ViewController: UIViewController, URLSessionTaskDelegate, URLSessionDelegate, URLSessionDataDelegate {
var httpString = "hided"
override func viewDidLoad() {
super.viewDidLoad()
getLogBinData()
}
func getLogBinData() {
let session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue.main)
if let url = URL(string: httpString + "log.bin") {
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = session.dataTask(with: request)
task.resume()
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
print()
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if dataTask.countOfBytesReceived >= 500 {
print(dataTask.countOfBytesReceived)
}
}
func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive response: URLResponse,
completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
if dataTask.countOfBytesReceived >= 500 {
print(dataTask.countOfBytesReceived)
}
}
}
according the comment, i edited code and it's worked.
class ViewController: UIViewController, URLSessionTaskDelegate, URLSessionDelegate, URLSessionDataDelegate {
var httpString = "hided"
override func viewDidLoad() {
super.viewDidLoad()
getLogBinData()
}
func getLogBinData() {
let session = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue.main)
if let url = URL(string: httpString + "log.bin") {
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = session.dataTask(with: request)
task.resume()
}
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if dataTask.countOfBytesReceived >= 500 {
print(dataTask.countOfBytesReceived)
}
}
}
I am trying to add background fetch capability to my app. Currently, the delegate function urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) is called after the network call is complete, but the URL in the cache directory does not exist:
class DownloadManager: NSObject, URLSessionTaskDelegate, URLSessionDownloadDelegate {
static var shared = DownloadManager()
var session : URLSession {
get {
let config = URLSessionConfiguration.background(withIdentifier: "my_Identifier")
return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print(location.absoluteString)
do {
let myData = try Data(contentsOf: location)
} catch let error {
print(error.localizedDescription)
// The The file “file_id.tmp” couldn’t be opened because there is no such file.
}
}
public func fetch() {
guard let url = URL(string: "#{myURL}") else {
return
}
let task = session.downloadTask(with: url)
task.resume()
}
}
And in my App Delegate:
func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("Executing background fetch")
DownloadManager.shared.fetch()
completionHandler(.newData)
}
What am I missing?
Try using this:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var progressView: UIProgressView!
override func viewDidLoad() {
let _ = DownloadManager.shared.activate()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DownloadManager.shared.onProgress = { (progress) in
OperationQueue.main.addOperation {
self.progressView.progress = progress //assign progress value
}
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
DownloadManager.shared.onProgress = nil
}
#IBAction func startDownload(_ sender: Any) {
let url = URL(string: "YourFileURL")!
DownloadManager.shared.download(url)
}
}
Replace your DownloadManager:
import Foundation
class DownloadManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
static var shared = DownloadManager()
var url : URL?
typealias ProgressHandler = (Float) -> ()
var onProgress : ProgressHandler? {
didSet {
if onProgress != nil {
let _ = activate()
}
}
}
override private init() {
super.init()
}
func activate() -> URLSession {
let config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).background")
// Warning: If an URLSession still exists from a previous download, it doesn't create a new URLSession object but returns the existing one with the old delegate object attached!
return URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue())
}
private func calculateProgress(session : URLSession, completionHandler : #escaping (Float) -> ()) {
session.getTasksWithCompletionHandler { (tasks, uploads, downloads) in
let progress = downloads.map({ (task) -> Float in
if task.countOfBytesExpectedToReceive > 0 {
return Float(task.countOfBytesReceived) / Float(task.countOfBytesExpectedToReceive)
} else {
return 0.0
}
})
completionHandler(progress.reduce(0.0, +))
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
if totalBytesExpectedToWrite > 0 {
if let onProgress = onProgress {
calculateProgress(session: session, completionHandler: onProgress)
}
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
debugPrint("Progress \(downloadTask) \(progress)")
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
debugPrint("Download finished: \(location)")
// try? FileManager.default.removeItem(at: location)
//copy downloaded data to your documents directory with same names as source file
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let destinationUrl = documentsUrl!.appendingPathComponent(url!.lastPathComponent)
let dataFromURL = try? Data(contentsOf: location)
try? dataFromURL?.write(to: destinationUrl, options: [.atomic])
print(destinationUrl)
//now it is time to do what is needed to be done after the download
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
debugPrint("Task completed: \(task), error: \(String(describing: error))")
}
func download(_ url: URL)
{
self.url = url
//download identifier can be customized. I used the "ulr.absoluteString"
let task = DownloadManager.shared.activate().downloadTask(with: url)
task.resume()
}
}
And in my App Delegate:
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
debugPrint("handleEventsForBackgroundURLSession: \(identifier)")
completionHandler()
}
Reference: Tutorial by ralfebert
I just added background downloading to my DownloadManager class but I have an issue right now !. For example I just add a file to download , in a table view I have some values like : progress , total bytes written and etc. When I press the home button the downloading will be continue in the background and will be finished but the processing stop there ! and not continue to make process 100% , or change downloaded file size.
The download task method :
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64){
DispatchQueue.main.async(execute: {() -> Void in
let db:Double = round(Double(totalBytesExpectedToWrite) / 10000)/100
//Added by me
let dbWritten:Double = round(Double(totalBytesWritten) / 10000)/100
self.fileSize = (NSString(format:"%.2f MB of %.2f MB", dbWritten,db)) as String
self.downloadProgress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
self.downloadProgressTxt = "\(String(Int(Double(self.downloadProgress*100)))) %";
self.delegate.updateProgress()
})
}
Start download function :
func startDownload(){
let defConfigObject = URLSessionConfiguration.background(withIdentifier: "com.App.backgoundSession")
let defaultSession = URLSession(configuration: defConfigObject, delegate: self, delegateQueue: OperationQueue())
downloadTask = defaultSession.downloadTask(with: fileUrl)
downloadTask.resume()
downloadStatus = "Downloading"
isDownloading = true
delegate.updateProgress()
}
In app delegate :
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
debugPrint("handleEventsForBackgroundURLSession: \(identifier)")
completionHandler()
}
How can I update func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask method when is in the background ?
And I enabled the background mode in capabilities :