Problem with downloading files.
I'm trying to download a file form url and push it into open/share modal. But da data downloads as Data and if I try saving it to File app it saves a file called Data.
I just need to download and share the file with the original extension. Like file.extension.
Here's the code use. I used Alamofire pod here:
AF.download(url).responseData { response in
if let preset = response.value {
let shareArray = [preset]
let activityViewController = UIActivityViewController(activityItems: shareArray , applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
}
Also tried this code but the app crashed:
if let url = URL(string: downloadURL!) {
let task = URLSession.shared.downloadTask(with: url) { localURL, urlResponse, error in
if let localURL = localURL {
let shareArray = [localURL]
let activityViewController = UIActivityViewController(activityItems: shareArray , applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
}
task.resume()
}
The issue there is that you are trying to share the temporary file returned. It has a dynamic UTI (Unified Type Identifier). You need to get the url response suggested file name and rename the file.
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
extension URL {
var typeIdentifier: String? { (try? resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier }
}
let url = URL(string: "https://i.stack.imgur.com/varL9.jpg")!
URLSession.shared.downloadTask(with: url) { location, response, error in
guard let location = location,
let httpURLResponse = response as? HTTPURLResponse,
httpURLResponse.statusCode == 200 else { return }
let fileName = httpURLResponse.suggestedFilename ?? httpURLResponse.url?.lastPathComponent ?? url.lastPathComponent
let destination = FileManager.default.temporaryDirectory.appendingPathComponent(fileName)
do {
if FileManager.default.fileExists(atPath: destination.path) {
try FileManager.default.removeItem(at: destination)
}
print("location", location.typeIdentifier ?? "no UTI") // location dyn.ah62d4rv4ge81k5pu
try FileManager.default.moveItem(at: location, to: destination)
print("destination", destination.typeIdentifier ?? "no UTI") // destination public.jpeg
} catch {
print(error)
}
}.resume()
To complete the answer from #leo Dabus, there is a need to modify the destination URL further and append the file extension you desire by using
FileManager.default.moveItem(at:to:)
https://developer.apple.com/documentation/foundation/filemanager/1414750-moveitem
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// create destination URL with the original pdf name
print("fileDownload: urlSession")
guard let url = downloadTask.originalRequest?.url else { return }
print("fileDownload: urlSession \(url)")
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let destinationURL = documentsPath.appendingPathComponent(url.lastPathComponent)
// delete original copy
try? FileManager.default.removeItem(at: destinationURL)
// append a new extension type
let newURL = destinationURL.deletingPathExtension().appendingPathExtension("pdf")
// copy from temp to Document
try? FileManager.default.moveItem(at: destinationURL, to: newURL)
do {
try FileManager.default.copyItem(at: location, to: newURL)
myViewDocumentsmethod(PdfUrl:destinationURL)
print("fileDownload: downloadLocation", destinationURL)
} catch let error {
print("fileDownload: error \(error.localizedDescription)")
}
}
Related
I am getting getting multiple PDF urls from server response and showing them in tableview with download option for each cell.
I am able to download each pdf file only once, But, Tried to second time download, It is showing already downloaded error.
How to fix this?
Here is my code
func downloadButtonTapped(index: Int) {
let finalUrlStr = "(dataResponse?[index].brochure)")
let fileURL = URL(string: finalUrlStr)
let fileName = String((fileURL!.lastPathComponent)) as NSString
// Create destination URL
let documentsUrl:URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let destinationFileUrl = documentsUrl.appendingPathComponent("\(fileName)")
//Create URL to the source file you want to download
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL!)
LoadingView.show()
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
do {
//Show UIActivityViewController to save the downloaded file
let contents = try FileManager.default.contentsOfDirectory(at: documentsUrl, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
for indexx in 0..<contents.count {
if contents[indexx].lastPathComponent == destinationFileUrl.lastPathComponent {
let activityViewController = UIActivityViewController(activityItems: [contents[indexx]], applicationActivities: nil)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
self.present(activityViewController, animated: true, completion: nil)
LoadingView.hide()
}
}
}
}
catch (let err) {
// DispatchQueue.main.async {
print("error: \(err.localizedDescription)")
LoadingView.hide()
self.showBasicAlert(title: "\(err.localizedDescription)", message: "")
// }
}
} catch (let writeError) {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
LoadingView.hide()
self.showBasicAlert(title: "\(writeError.localizedDescription)", message: "")
}
} else {
print("Error took place while downloading a file. Error description: \(error?.localizedDescription ?? "")")
self.showBasicAlert(title: "\(error?.localizedDescription ?? "")", message: "")
}
}
task.resume()
}
Even though, PDF not downloaded even one time before download file, activitycontroller displaying and If we close that without download/save file, Again trying to download, Same error message showing like already exist file
How to download multiple times like whenever user taps on download option, It should download the pdf file.
Also after downloaded pdf, I need to show open pdf from external not inside app
Any suggestions?
I have fixed it by myself by removing condition.
Now I am able to download whenever user tap on download button.
Here is the code.
func downloadButtonTapped(index: Int) {
// print("index \(index)")
let finalUrlStr = "(dataResponse?[index].brochure)")
let fileURL = URL(string: finalUrlStr)
if let fileUrl = fileURL {
let fileName = String((fileUrl.lastPathComponent)) as NSString
// Create destination URL
let documentsUrl:URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let destinationFileUrl = documentsUrl.appendingPathComponent("\(fileName)")
//Create URL to the source file you want to download
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileUrl)
LoadingView.show()
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
//Show UIActivityViewController to save the downloaded file
let contents = try FileManager.default.contentsOfDirectory(at: documentsUrl, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
for indexx in 0..<contents.count {
if contents[indexx].lastPathComponent == destinationFileUrl.lastPathComponent {
let activityViewController = UIActivityViewController(activityItems: [contents[indexx]], applicationActivities: nil)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
self.present(activityViewController, animated: true, completion: nil)
LoadingView.hide()
}
}
}
}
catch (let err) {
print("error: \(err.localizedDescription)")
DispatchQueue.main.async {
LoadingView.hide()
self.showBasicAlert(title: "\(err.localizedDescription)", message: "")
}
}
}
}
task.resume()
}
}
I am downloading pdf from url and saving to .documentsDirectory. but it's saving somewhere inside app data, instead I want to save it on phone.
func downnload(url: NSURL, filename:String) {
let fileName = String((url.lastPathComponent!)) as NSString
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)!
let destinationFileUrl = documentsUrl.appendingPathComponent("\(fileName)")
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:url as URL)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
do {
let contents = try FileManager.default.contentsOfDirectory(at: documentsUrl, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
for indexx in 0..<contents.count {
if contents[indexx].lastPathComponent == destinationFileUrl.lastPathComponent {
let activityViewController = UIActivityViewController(activityItems: [contents[indexx]], applicationActivities: nil)
self.present(activityViewController, animated: true, completion: nil)
}
}
}
catch (let err) {
print("error: \(err)")
}
} catch (let writeError) {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: \(error?.localizedDescription ?? "")")
}
}
task.resume()
}
}
You can save your file in to Apples' Files App using UIDocumentInteractionController
After downloading pdf from url and saving to .documentsDirectory, Give your documentsDirectory file url to UIDocumentInteractionController
let documentInteractionController = UIDocumentInteractionController()
documentInteractionController.url = url //Here is your documentsDirectory file url
documentInteractionController.uti = url.typeIdentifier ?? "public.data, public.content"
documentInteractionController.name = url.localizedName ?? url.lastPathComponent
documentInteractionController.presentOptionsMenu(from: view.frame, in: view, animated: true)
Here I'm using extension for URL
extension URL {
var typeIdentifier: String? {
return (try? resourceValues(forKeys: [.typeIdentifierKey]))?.typeIdentifier
}
var localizedName: String? {
return (try? resourceValues(forKeys: [.localizedNameKey]))?.localizedName
}
}
When I try to download the file from the url the files gets downloaded and is stored in files folder. But when I try to open the file. The file appears to be corrupted and I am not able to open the pdf file.
I have tried all the possibilites using the following code mentioned. But none of them worked. Any solution thanks in advance.
static func downloadFileFromUrl(urlString:String, fileName:String,viewController : UIViewController) {
// Create destination URL
if let documentsUrl:URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first{
let destinationFileUrl = documentsUrl.appendingPathComponent("\(fileName)")
//Create URL to the source file you want to download
let fileURL = URL(string: urlString)
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url:fileURL!)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
try? FileManager.default.removeItem(at: destinationFileUrl)
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
do {
//Show UIActivityViewController to save the downloaded file
let contents = try FileManager.default.contentsOfDirectory(at: documentsUrl, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
for indexx in 0..<contents.count {
if contents[indexx].lastPathComponent == destinationFileUrl.lastPathComponent {
let activityViewController = UIActivityViewController(activityItems: [contents[indexx]], applicationActivities: nil)
viewController.present(activityViewController, animated: true, completion: nil)
}
}
}
catch (let err) {
UIApplication.shared.keyWindow?.makeToast(message: err.localizedDescription)
}
} catch (let writeError) {
DispatchQueue.main.async(execute: {
UIApplication.shared.keyWindow?.makeToast(message: "Error creating a file :- \(writeError.localizedDescription)")
})
}
} else {
UIApplication.shared.keyWindow?.makeToast(message: "Error downloading the file")
}
}
task.resume()
}
}
Now the file after downloading is not opening. Is there any problem with code mentioned to download file from url
CompletionHandler of downloadTask Method is performed in Global (Background) Queue. You have to present your activityViewController in Main Queue.
Use the following code to present your activityViewController:
DispatchQueue.main.async(execute: {
let activityViewController = UIActivityViewController(activityItems: [contents[indexx]], applicationActivities: nil)
viewController.present(activityViewController, animated: true, completion: nil)
})
instead of just
let activityViewController = UIActivityViewController(activityItems: [contents[indexx]], applicationActivities: nil)
viewController.present(activityViewController, animated: true, completion: nil)
I am trying to download an audio file from the internet and save it onto the phone. This is the download function:
func download() {
if let audioUrl = downloadUrl {
// 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(audioUrl.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")
// if the file doesn't exist
} else {
// you can use NSURLSession.sharedSession to download the data asynchronously
URLSession.shared.downloadTask(with: audioUrl, 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")
} catch let error as NSError {
print(error.localizedDescription)
}
}).resume()
}
}
}
Then, after I close and open the app, I use the following function to retrieve the url and play it using an AVPlayer:
func getUrl2() {
if let audioUrl = downloadUrl {
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
if let u = self.destinationUrl {
let player = AVPlayer(url: u)
print(u)
print("Bouta play")
print(CMTimeGetSeconds(player.currentItem!.duration))
player.play()
}
}
}
The duration that keeps getting printed out is "nan". Is there a way to check if the audio file is actually downloading? Or could it be a problem with retrieving the file after the download? Thanks in advance.
First of all you have to check for the URL is not empty with the below logic:
if !link.isEmpty{
checkBookFileExists(withLink: link){ [weak self] downloadedURL in
guard let self = self else{
return
}
play(url: downloadedURL)
}
}
Then checkBookFileExists function will check if the file already saved or not before download it again:
func checkBookFileExists(withLink link: String, completion: #escaping ((_ filePath: URL)->Void)){
let urlString = link.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
if let url = URL.init(string: urlString ?? ""){
let fileManager = FileManager.default
if let documentDirectory = try? fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create: false){
let filePath = documentDirectory.appendingPathComponent(url.lastPathComponent, isDirectory: false)
do {
if try filePath.checkResourceIsReachable() {
print("file exist")
completion(filePath)
} else {
print("file doesnt exist")
downloadFile(withUrl: url, andFilePath: filePath, completion: completion)
}
} catch {
print("file doesnt exist")
downloadFile(withUrl: url, andFilePath: filePath, completion: completion)
}
}else{
print("file doesnt exist")
}
}else{
print("file doesnt exist")
}
}
Then if the file doesn't exists you will download it with the below function:
func downloadFile(withUrl url: URL, andFilePath filePath: URL, completion: #escaping ((_ filePath: URL)->Void)){
DispatchQueue.global(qos: .background).async {
do {
let data = try Data.init(contentsOf: url)
try data.write(to: filePath, options: .atomic)
print("saved at \(filePath.absoluteString)")
DispatchQueue.main.async {
completion(filePath)
}
} catch {
print("an error happened while downloading or saving the file")
}
}
}
That function will save it and you can play it with:
func play(url: URL) {
print("playing \(url)")
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer?.prepareToPlay()
audioPlayer?.delegate = self
audioPlayer?.play()
let percentage = (audioPlayer?.currentTime ?? 0)/(audioPlayer?.duration ?? 0)
DispatchQueue.main.async {
// do what ever you want with that "percentage"
}
} catch let error {
audioPlayer = nil
}
}
I want to attach an image to my local notifications given an image URL. This is the extension to create an attachment:
import UserNotifications
extension UNNotificationAttachment {
static func create(identifier: String, image: UIImage, options: [NSObject : AnyObject]?) -> UNNotificationAttachment? {
let fileManager = FileManager.default
let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
let tmpSubFolderURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
do {
try fileManager.createDirectory(at: tmpSubFolderURL, withIntermediateDirectories: true, attributes: nil)
let imageFileIdentifier = identifier+".png"
let fileURL = tmpSubFolderURL.appendingPathComponent(imageFileIdentifier)
guard let imageData = UIImagePNGRepresentation(image) else {
return nil
}
try imageData.write(to: fileURL)
let imageAttachment = try UNNotificationAttachment.init(identifier: imageFileIdentifier, url: fileURL, options: options)
return imageAttachment } catch {
print("error " + error.localizedDescription)
}
return nil
}
}
When I schedule a new notification, I use it like this:
// url of the image such as http://www.unsplash.com/image.png
let data = try? Data(contentsOf: url)
guard let myImage = UIImage(data: data!) else { return }
if let attachment = UNNotificationAttachment.create(identifier: key, image: myImage, options: nil) {
content.attachments = [attachment]
}
Creating a notification like this freezes the application for a few seconds because the app downloads the image synchronously. I have also tried to use DispatchQueue but it didn't change anything. What did I do wrong?
Your code downloads an image, parses it to create a UIImage, converts the image back to a block of PNG data, then writes this data to a temporary file.
You can skip the step where you create the UIImage and convert it back to a file.
Try using URLSession and URLDataTask:
let fileURL = ...
let task = URLSession.shared.dataTask(with: url) { (data, _, _) in
do {
try imageData.write(to: fileURL)
let attachment = UNNotificationAttachment.create(identifier: key, image: myImage, options: nil)
// call closure to call back with attachment and/or error
}
catch let ex {
// call closure with error
}
}
task.resume()
I've left out some error handling and other details, but this should give you the general idea of what's required to do it asynchronously. URLSessions use GCD to perform asynchronous networking.
Download the image asynchronously using Alamofire then try to show it.
let destination: DownloadRequest.DownloadFileDestination = {
_, _ in
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
documentsURL.appendPathComponent("image.jpg")
return (documentsURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(url, to: destination).response {
response in
// do whatever you want with your image, for example if it is an audio file:
do {
self.player = try AVAudioPlayer(contentsOf: URL(string: "\(response.destinationURL!)")!)
self.player.volume = 1.0
self.player.play()
} catch {
print(error)
}
}