How to download PDF using URL in iOS app multiple times - ios

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()
}
}

Related

Download and save pdf into files from url in swift

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
}
}

Download file with right extension

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)")
}
}

How do I show downloaded videos in iOS Storage settings

I've created a streaming video app that also downloads videos locally. How can I display downloaded videos in the iPhone/iPad Storage sections of settings?
I'm downloading using background tasks, and on complete running
let docsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let destinationUrl = docsUrl.appendingPathComponent(videoId + ".mp4")
let fileManager = FileManager.default
try? fileManager.removeItem(at: destinationUrl)
do {
try fileManager.copyItem(at: location, to: destinationUrl)
} catch let error {
print("Could not copy file to disk: \(error.localizedDescription)")
return
}
Apps like Netflix, Disney+ and Prime Video all show the downloaded shows and allow them to be deleted individually, but I haven't been able to figure out how it's done. All searches usually lead to guides for users on how to delete videos.
Anybody have any tips?
This code is working for me:
func downloadVideo1(videoString :String)
{
let myStringArr = videoString.components(separatedBy: "/")
let finalString = myStringArr[myStringArr.count - 1]
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: OperationQueue.main)
let docsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let destinationUrl = docsUrl.appendingPathComponent(finalString)
if(FileManager().fileExists(atPath: destinationUrl.path)){
print("\n\nfile already exists\n\n")
}
else{
// setupProgress()
//DispatchQueue.global(qos: .background).async {
var request = URLRequest(url: URL(string: videoString)!)
request.httpMethod = "GET"
_ = session.dataTask(with: request, completionHandler: { (data, response, error) in
if(error != nil){
print("\n\nsome error occured\n\n")
return
}
if let response = response as? HTTPURLResponse{
if response.statusCode == 200{
DispatchQueue.main.async {
if let data = data{
if let _ = try? data.write(to: destinationUrl, options: Data.WritingOptions.atomic){
print("\n\nurl data written\n\n")
print(destinationUrl)
self.checkCount = self.checkCount + 1
if self.checkCount == self.availableMediaCount
{
self.progressView.isHidden = true
let vc = TeacherLessonPlanSB.instantiateViewController(withIdentifier: "DownloadLessonVC") as! DownloadLessonViewController
self.navigationController?.pushViewController(vc, animated: true)
}
}
else{
print("\n\nerror again\n\n")
}
}//end if let data
}//end dispatch main
}//end if let response.status
}
}).resume()
}
}

After downloading the file from url using urlsessiondownloadtask the file is not opening

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)

How to download and save an audio file and then play it in swift?

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
}
}

Resources