I have the following code that allows me to download a PDF file from a URL, it works correctly:
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
}
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
DownloadManager.shared.onProgress = nil
}
#IBAction func startDownload(_ sender: Any) {
let url = URL(string: "https://d0.awsstatic.com/whitepapers/KMS-Cryptographic-Details.pdf")!
let task = DownloadManager.shared.activate().downloadTask(with: url)
task.resume()
}
}
The file is currently going to: file:///Users/cybermac/Library/Developer/CoreSimulator/Devices/CAEC75D0-423A-4FB2-B0D6-9E7CADB190A1/data/Containers/Data/Application/8B5CBFC8-7058-48DB-A1C4-872302A80610/Library/Caches/com.apple.nsurlsessiond/Downloads/com.example.DownloadTaskExample/CFNetworkDownload_Q7OVlf.tmp
How do I save it in /Documents/?
Something like this: file:///Users/cybermac/Library/Developer/CoreSimulator/Devices/CAEC75D0-423A-4FB2-B0D6-9E7CADB190A1/data/Containers/Data/Application/64370B29-2C01-470F-AE76-17EF1A7BC918/Documents/
The idea is that the file saved in that directory can be used to read it offline (with PDFKit or webKit). It will only be deleted if the application is deleted.
You need to move the file to your custom location after the download. Implement URLSessionDownloadDelegate and you will receive the location of your downloaded file.
Delegate method:
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)
Code to move the file:
do {
let documentsURL = try
FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
let savedURL = documentsURL.appendingPathComponent("yourCustomName.pdf")
try FileManager.default.moveItem(at: location, to: savedURL)
} catch {
print ("file error: \(error)")
}
To learn more refer to this repo.
This snippet was downloaded your Remote PDF from Server and "Save" into your Photo Library
func drawPDFfromURL(url: URL) -> UIImage? {
guard let document = CGPDFDocument(url as CFURL) else { return nil }
guard let page = document.page(at: 1) else { return nil }
let pageRect = page.getBoxRect(.mediaBox)
let renderer = UIGraphicsImageRenderer(size: pageRect.size)
let img = renderer.image { ctx in
UIColor.white.set()
ctx.fill(pageRect)
ctx.cgContext.translateBy(x: 0.0, y: pageRect.size.height)
ctx.cgContext.scaleBy(x: 1.0, y: -1.0)
ctx.cgContext.drawPDFPage(page)
}
return img
}
step 2
UIImageWriteToSavedPhotosAlbum(drawPDFfromURL(url: url!) ?? UIImage(), nil, nil, nil)
Related
The app needs to create a URL to pass into a UIDocumentInteractionController that will present the user with options to share the file. It's a PDF that is downloaded over the network and confirmed as being in place.
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let filePath = documentsPath.appendingPathComponent("documents/\(self.filename!)").path
let fileURL = URL(fileURLWithPath: filePath)
This is where the PDF is stored after being downloaded.
let documentController: UIDocumentInteractionController = UIDocumentInteractionController.init(url: fileURL)
documentController.uti = "com.adobe.pdf"
documentController.presentOpenInMenu(from: CGRect(x: 0, y: 0, width: self.container.frame.width, height: self.container.frame.height), in: self.container, animated: true)
This opens up the menu with the sharing options but when trying to select an app to open the PDF, the following error occurs:
Could not instantiate class NSURL. Error: Error Domain=NSCocoaErrorDomain Code=4864 "The URL archive of type “public.url” contains invalid data." UserInfo={NSDebugDescription=The URL archive of type “public.url” contains invalid data.}
After having confirmed the document is where it should be, when printing the fileURL, it's the following:
file:///var/mobile/Containers/Data/Application/3296B736-4DFB-4F62-9B05-C800D574982B/Documents/documents/77351848-68816600-1626168959.pdf
I have searched high and low for an answer to this and I believe it's more linked to the URL than the UIDocumentInteractionController itself.
Any advice would be enormously appreciated.
Swift 5 Implementation(Xcode 12.5):
class VC1: UIViewController {
var pdfURL: URL?
var documentInteractionController:UIDocumentInteractionController!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func downloadAction(_ sender: Any) {
self.downloadPDF()
}
func downloadPDF() {
guard let url = URL(string: "https://file-examples-com.github.io/uploads/2017/10/file-sample_150kB.pdf") else { return }
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue())
let downloadTask = urlSession.downloadTask(with: url)
downloadTask.resume()
}
func showPDF(url: URL) {
documentInteractionController = UIDocumentInteractionController(url: url)
documentInteractionController.delegate = self
DispatchQueue.main.async { [self] in
documentInteractionController.presentPreview(animated: true)
}
}
}
extension VC1: UIDocumentInteractionControllerDelegate {
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self
}
func documentInteractionControllerDidEndPreview(_ controller: UIDocumentInteractionController) {
print("Dismissed!!!")
documentInteractionController = nil
}
}
extension VC1: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
guard let url = downloadTask.originalRequest?.url else { return }
let documentsPath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
let destinationURL = documentsPath.appendingPathComponent(url.lastPathComponent)
try? FileManager.default.removeItem(at: destinationURL)
do {
try FileManager.default.copyItem(at: location, to: destinationURL)
self.pdfURL = destinationURL
showPDF(url: destinationURL)
} catch let error {
print("Error: \(error.localizedDescription)")
}
}
}
I include VNDocumentCameraViewControllerDelegate in my class
To "scan the document", I used this code:
#IBAction func scanButton(_ sender: UIButton) {
let scannerViewController = VNDocumentCameraViewController()
scannerViewController.delegate = self
present(scannerViewController, animated: true)
}
In viewDidLoad, I include the PDFView
override func viewDidLoad() {
super.viewDidLoad()
pdfView = PDFView()
pdfView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(pdfView)
pdfView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
pdfView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
pdfView.topAnchor.constraint(equalTo: startOutlet.bottomAnchor).isActive = true
pdfView.bottomAnchor.constraint(equalTo: PDFOutlet.topAnchor).isActive = true
PDFOutlet.isHidden = true }
I add the documentCameraViewController functions
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
guard scan.pageCount >= 1 else {
controller.dismiss(animated: true)
return
}
DispatchQueue.main.async {
let pdfDocument = PDFDocument()
for i in 0 ..< scan.pageCount {
if let image = scan.imageOfPage(at: i).resize(toWidth: 250) {
print("image size is \(image.size.width), \(image.size.height)")
// Create a PDF page instance from your image
let pdfPage = PDFPage(image: image)
// Insert the PDF page into your document
pdfDocument.insert(pdfPage!, at: i)
}
}
// Retrieve raw data of your PDF document
let data = pdfDocument.dataRepresentation()
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let docURL = documentDirectory.appendingPathComponent("Scanned-Docs.pdf")
do {
try data?.write(to: docURL)
}
catch(let error)
{
print("error is \(error.localizedDescription)")
}
}
controller.dismiss(animated: true)
PDFOutlet.isHidden = false
}
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error) {
print(error)
controller.dismiss(animated: true)
}
func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
controller.dismiss(animated: true)
}
}
extension SnapToPDF: UINavigationControllerDelegate {
// Not used, but necessary for compilation
}
extension UIImage {
func resize(toWidth width: CGFloat) -> UIImage? {
let canvas = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))
return UIGraphicsImageRenderer(size: canvas, format: imageRendererFormat).image {
_ in draw(in: CGRect(origin: .zero, size: canvas))
}
}
I want to have a button for users to click the save the PDF that they have selected, but this part of the code isn't working. Everything else above works, and in the app, I can take a photo and resize it, or choose from photo library. Ideally, wanted to show options for the user to save the PDF into their Files in the iPhone, but it is not working. The button does nothing. Please help!
#IBAction func viewPDFButton(_ sender: UIButton) {
let fileManager = FileManager.default
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
let docURL = documentDirectory.appendingPathComponent("Scanned-Docs.pdf")
if fileManager.fileExists(atPath: docURL.path){
pdfView.document = PDFDocument(url: docURL)
}
else {
print("file does not exist..")
}
}
is it possible to retrieve the recorded audio file from the document directory? I have looked through the whole internet, but using AVAsset/WKAudioFileAsset does not seem to be the right path.
Is there just the way to convert the recorded audio file to NSData (binary format), send it to the server and then the server will convert the binary data back into the audio file?
I have found this here: https://iosdevcenters.blogspot.com/2016/04/save-and-get-image-from-document.html
(This example is about retrieving images and they have used UIImage)
So, I am curious whether there is an equivalent way for retrieving an audio file? Is there a class for audio to save the file into that class just as UIImage for images?
I hope someone can help me out. Thank you very much.
My code looks as follows:
import WatchKit
import Foundation
import AVFoundation
class InterfaceController: WKInterfaceController, AVAudioRecorderDelegate{
#IBOutlet weak var btn: WKInterfaceButton!
var recordingSession : AVAudioSession!
var audioRecorder : AVAudioRecorder!
var settings = [String : Any]()
override func awake(withContext context: Any?) {
super.awake(withContext: context)
recordingSession = AVAudioSession.sharedInstance()
do{
try recordingSession.setCategory(AVAudioSession.Category.playAndRecord)
try recordingSession.setActive(true)
recordingSession.requestRecordPermission(){[unowned self] allowed in
DispatchQueue.main.async {
if allowed{
print("Allow")
} else{
print("Don't Allow")
}
}
}
}
catch{
print("failed to record!")
}
// Configure interface objects here.
// Audio Settings
settings = [
AVFormatIDKey:Int(kAudioFormatLinearPCM),
AVSampleRateKey:44100.0,
AVNumberOfChannelsKey:1,
AVLinearPCMBitDepthKey:8,
AVLinearPCMIsFloatKey:false,
AVLinearPCMIsBigEndianKey:false,
AVEncoderAudioQualityKey:AVAudioQuality.max.rawValue
]
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
print("Test")
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
func directoryURL() -> URL? {
let fileManager = FileManager.default
let urls = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
let documentDirectory = urls[0] as URL
let soundUrl = documentDirectory.appendingPathComponent("sound.wav")
// var soundUrlStr = soundUrl?.path
//print(fileManager.fileExists(atPath: soundUrlStr))
let filePath = (soundUrl).path
print(filePath)
print("URL")
print(soundUrl)
return soundUrl as URL?
}
func startRecording(){
let audioSession = AVAudioSession.sharedInstance()
do{
audioRecorder = try AVAudioRecorder(url: self.directoryURL()! as URL,
settings: settings)
audioRecorder.delegate = self
audioRecorder.prepareToRecord()
audioRecorder.record(forDuration: 5.0)
}
catch {
finishRecording(success: false)
}
do {
try audioSession.setActive(true)
audioRecorder.record()
} catch {
}
}
func finishRecording(success: Bool) {
audioRecorder.stop()
if success {
print(success)
} else {
audioRecorder = nil
print("Somthing Wrong.")
}
}
#IBAction func recordAudio() {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = URL(fileURLWithPath: path)
let pathPart = url.appendingPathComponent("sound.wav")
let filePath = pathPart.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath){
print("File exists!")
let audioAsset = WKAudioFileAsset(url:pathPart)
// let playerItem = WKAudioFilePlayerItem(asset:audioAsset)
print("Audio File")
print(audioAsset)
print("WAV file")
print(NSData(contentsOfFile: filePath) as Any)
}else{
print("File does not exist")
}
if audioRecorder == nil {
print("Pressed")
self.btn.setTitle("Stop")
self.btn.setBackgroundColor(UIColor(red: 119.0/255.0, green: 119.0/255.0, blue: 119.0/255.0, alpha: 1.0))
self.startRecording()
} else {
self.btn.setTitle("Record")
print("Pressed2")
self.btn.setBackgroundColor(UIColor(red: 221.0/255.0, green: 27.0/255.0, blue: 50.0/255.0, alpha: 1.0))
self.finishRecording(success: true)
}
}
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
if !flag {
finishRecording(success: false)
}
}
}
My application have file download option which downloads the file using alamofire download method. When the download completes i need to present the preview of the file without saving it to internal/cloud storage. How can i achieve this whatsapp like function that shows the preview after downloading the file.
func downloadFile(fileUrl: URL) {
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
Alamofire.download(fileUrl, to: destination)
.response(completionHandler: { (downloadResponse) in
self.dic.url = downloadResponse.destinationURL
self.dic.uti = downloadResponse.destinationURL!.uti
let rect = CGRect(x: 0, y: 0, width: 100, height: 100)
self.dic.presentOpenInMenu(from: rect, in: self.view, animated: true)
})
}
To present a preview of a file use Apple’s QuickLook framework that lets you embed previewing for a huge range of file types, including iWork documents, Microsoft Office documents, PDFs, images, and more, all without writing much code.
First, import the QuickLook framework, then make your view controller conform to the QLPreviewControllerDataSource protocol.
Reference:
https://www.hackingwithswift.com/example-code/libraries/how-to-preview-files-using-quick-look-and-qlpreviewcontroller
https://github.com/gargsStack/QLPreviewDemo
https://www.appcoda.com/quick-look-framework/
Code:
class ViewController: UIViewController {
var previewItem = URL!
func downloadFile(fileUrl: URL) {
let destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)
Alamofire.download(fileUrl, to: destination)
.response(completionHandler: { (downloadResponse) in
let previewController = QLPreviewController()
previewController.dataSource = self
self.previewItem = downloadResponse.destinationURL
self.present(previewController, animated: true, completion: nil)
})
}
}
extension ViewController: QLPreviewControllerDataSource {
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return self.previewItem as QLPreviewItem
}
}
Here is one solution using Alamofire. Someone may help.
Steps:
Alamofire has excellent staff to direct download and also save/write
your file into disc.
Return a path where downloaded file saved.
Using UIDocumentInteractionController pass the file path
Then present this view
import Alamofire
extension UIViewController : UIDocumentInteractionControllerDelegate{
func downloadFileForPreview(fileName: String, fileExtension: String, filePath: String ) {
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileWithExtension = "file.\(fileExtension)"
let fileURL = documentsURL.appendingPathComponent(fileWithExtension)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
//UtilitySwift.showUniversalLoadingView(true)
Alamofire.download(filePath, to: destination).response { response in
debugPrint(response)
//UtilitySwift.showUniversalLoadingView(false)
if response.error == nil, let storeFilePath = response.destinationURL?.path {
//let image = UIImage(contentsOfFile: imagePath)
self.previewDocument(withFilePath: response.destinationURL)
print(storeFilePath)
}
else{
UtilitySwift.showErrorMessage(message: response.error?.localizedDescription ?? "Error occured when downloading" )
print(response.error?.localizedDescription ?? "")
}
}
}
// Converted to Swift 5.1 by Swiftify v5.1.29672 - https://objectivec2swift.com/
func previewDocument(withFilePath filePath: URL?) {
var documentInteractionController: UIDocumentInteractionController?
if filePath != nil {
// Initialize Document Interaction Controller
if let filePath = filePath {
documentInteractionController = UIDocumentInteractionController(url: filePath)
}
// Configure Document Interaction Controller
documentInteractionController?.delegate = self as UIDocumentInteractionControllerDelegate
//if not possible to open
if !documentInteractionController!.presentPreview(animated: true) {
documentInteractionController?.presentOptionsMenu(from: CGRect.zero, in: self.view, animated: true)
}
} else {
// error
print("file preview error")
}
}
//UIDocumentInteractionControllerDelegate
public func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
self
}
}
Call from any UIViewController
self.downloadFileForPreview(fileName: "file", fileExtension: fileExt ?? "", filePath: REPLACE_WITH_DOWNLOAD_URL)
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 :)