I download and display PDF from my swift application using this code:
var docController:UIDocumentInteractionController!
let pdfUrl = NSURL(string: "ENTER_URL_OF_PDF")
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
downloadDoc(pdfUrl: pdfUrl!)
}
#IBAction func buttonAction(_ sender: AnyObject) {
docController.presentOptionsMenu(from: self.view.frame, in: self.view, animated: true)
}
func downloadDoc(pdfUrl : NSURL) {
let urlTest = self.pdfUrl!.absoluteString
let pdfUrl = NSURL(string: urlTest!)
if(pdfUrl != nil){
let pdfRequest: NSURLRequest = NSURLRequest(url: pdfUrl! as URL)
NSURLConnection.sendAsynchronousRequest(pdfRequest as URLRequest, queue: OperationQueue.main) {(response, data, error) in
let httpResponse = response as? HTTPURLResponse
if(httpResponse?.statusCode == 200 && error == nil){
let documentsUrl = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).first as! NSURL
if let fileName = self.pdfUrl!.lastPathComponent {
let destinationUrl = documentsUrl.appendingPathComponent(fileName)
if let data = data {
do {
try data.write(to: destinationUrl!, options: .atomic)
} catch {
print(error)
}
self.docController = UIDocumentInteractionController(url: destinationUrl!)
}
}
}
}
}
}
The problem I'm facing is that when I try with a PDF containing grater than 3 pages, I got this error:
fatal error: unexpectedly found nil while unwrapping an Optional value
and my app crash. When I searched about the max size that can be downloaded I read in man SO posts that there is no limit!
So why I got this problem?
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)")
}
}
}
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)")
}
}
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()
}
}
I am trying to play a video using UIWebView, but it's not showing any video even though the video is downloading from the server. Does anyone know what I'm doing wrong here?
Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
self.pdfView.delegate = self
self.pdfView.mediaPlaybackRequiresUserAction = false
if "" != video?.videoPath {
self.loadFromUrl(path: (video?.videoPath)!)
self.activityIND.isHidden = true
self.activityIND.stopAnimating()
} else {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let strName = video?.id
let filePath = "\(documentsPath)/"+strName!+".wmv"
let fileManager = FileManager.default
self.activityIND.startAnimating()
if fileManager.fileExists(atPath: filePath) {
self.loadFromUrl(path: filePath)
return;
}
let reference = FIRStorage.storage().reference(forURL: (self.video?.videoURL)!)
reference.data(withMaxSize: 50 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
print ("unable to download video file from Firebase Storage")
self.activityIND.isHidden = false
self.activityIND.startAnimating()
} else {
if ((try! data?.write(to: URL.init(fileURLWithPath: filePath, isDirectory: false))) != nil) {
self.loadFromUrl(path: filePath)
print ("video file is downloaded from Firebase Storage")
self.db.upDate(id: (self.video?.id)!, videoPath: filePath)
self.activityIND.isHidden = true
}
}
}
}
}
func loadFromUrl(path: String)
{
let url = NSURL(string:path)
pdfView.loadRequest(NSURLRequest(url: url! as URL) as URLRequest)
activityIND.isHidden = true
activityIND.startAnimating()
}
turns out wmv format is not supported, once i changed the format to mp4 everything worked
I am following a tutorial about getting images from the web and storing them on the phone in Swift. For my purpose, I would like to know how I could only store them for one 'session', which means until the user stops using the app. The reason is that I want to change the image of the url every day.
Anyone any idea?
#IBOutlet var overLay: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = NSURL(string: "http://test.com")
// Update - changed url to url!
let urlRequest = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
if error != nil {
println("There was an error")
} else {
let image = UIImage(data: data)
// self.overLay.image = image
var documentsDirectory:String?
var paths:[AnyObject] = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
if paths.count > 0 {
documentsDirectory = paths[0] as? String
var savePath = documentsDirectory! + "/overLay.jpg"
NSFileManager.defaultManager().createFileAtPath(savePath, contents: data, attributes: nil)
self.overLay.image = UIImage(named: savePath)
}
}
})
}
thank you so much!
Since you're only interested in keeping the image for the lifecycle of the app, it's perfectly viable to just hold a pointer to a UIImage object in memory, likely via some long-living object (AppDelegate would be a possible choice here).
Since you already have a UIImage from the data coming down the pipe, I'd simplify your code as such, or if you want to use some Singleton like the AppDelegate to manage the image state, see what happens when iWantToUseAppDelegate is set to true
#IBOutlet var overLay: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let iWantToUseAppDelegate = false // for demonstration purposes
let url = NSURL(string: "http://test.com")
// Update - changed url to url!
let urlRequest = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
if error != nil {
println("There was an error")
} else {
let image = UIImage(data: data)
if iWantToUseAppDelegate {
let appDelegate = UIApplication.sharedApplication().delegate as! YourAppDelegateClass // YourAppDelegateClass has some property called "cachedImage"
appDelegate.cachedImage = image
self.overLay.image = appDelegate.cachedImage
} else {
self.overLay.image = image
}
}
})
}
You may need to tweak a few things but this code might work a little easier.
Used what mindfreek add to correct the code.
#IBOutlet var overLay: UIImageView!
var defaults: NSUserDefaults = NSUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = NSURL(string: "http://test.com")
// Update - changed url to url!
let urlRequest = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
if error != nil {
println("There was an error")
} else {
let image = UIImage(data: data)
NSUserDefaults().setObject(NSKeyedArchiver.archivedDataWithRootObject(image!), forKey: "image")
if let imagedSaved: AnyObject = defaults.valueForKey("image")
{ overLay.image = image }
else { NSKeyedUnarchiver.unarchiveObjectWithData(NSUserDefaults().dataForKey("image")!) as UIImage }
}
})