Upload a pdf file with alamofire - ios

I got the following function which got created from swagger code gen:
open class func uploadFile(firstname: String, lastname: String, timestamp: Date, file: URL, completion: #escaping ((_ data: ApiResponse?,_ error: Error?) -> Void)) {
In the app you can make an image with the camera and the image got converted to a pdf file:
let document = PDFDocument()
let pdfPage = PDFPage(image: unwrapImage)
document.insert(pdfPage!,at: 0)
So now I want to upload this document. But document.documentURL is always nil. Although I can display the pdf docuemnt on the display. Am I supposed to save the pdf document to a temp directory to use the function with the url parameter?

PDFDocument's property documentURL is get only. If you do not use the url initializer it will always return nil. What you need is to get your PDFDocument dataRepresentation and write the pdf data to a url at a temporary or permanent location. Then you can upload its URL.
let document = PDFDocument()
let image = UIImage(named: "imageName.jpg")!
if let pdfPage = PDFPage(image: image) {
document.insert(pdfPage,at: 0)
do {
print(FileManager.default.temporaryDirectory.path)
let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent("pdfName.pdf")
try document.dataRepresentation()?.write(to: fileURL)
// upload your fileURL or copy to a permanent location
} catch {
print(error)
}
}

Related

Download a pdf from Firebase and store it locally on iOS

I need to download a pdf from the storage and save it locally on an iOS device, so it can be seen in Files.
Here is the code is taken from the docs, which I'm using:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let userID = Auth.auth().currentUser!.uid
print(userID)
// Get a reference to the storage service using the default Firebase App
let storage = Storage.storage()
// Create a storage reference from our storage service
let storageRef = storage.reference()
// Create a reference to the file you want to download
let islandRef = storageRef.child("pdf/sample.pdf")
// Create local filesystem URL
let localURL = URL(string: "pdf/sample.pdf")!
// Download to the local filesystem
let downloadTask = islandRef.write(toFile: localURL) { url, error in
if let error = error {
// Uh-oh, an error occurred!
} else {
// Local file URL for "images/island.jpg" is returned
}
}
}
When I try to run this ViewController, it doesn't crash but throws the following error:
"The file couldn’t be opened because the specified URL type isn’t supported." UserInfo={NSURL=pdf/sample.pdf}
The file in the Firebase Storage is saved in a folder called pdf/sample.pdf. Eventually, I wish to take the reference from the storage and pass it in a RealtimeDatabase, so the user can download it by viewing details about it in a table view.
I think what need to do is to specify in which path to your local filesystem you want to save the downloaded document. So let say you want to use the temporary folder to save your pdf. You can try the following:
let tmporaryDirectoryURL = FileManager.default.temporaryDirectory
let localURL = tmporaryDirectoryURL.appendingPathComponent("sample.pdf")
islandRef.write(toFile: localURL) { url, error in
if let error = error {
print("\(error.localizedDescription)")
} else {
self.presentActivityViewController(withUrl: url)
}
}
Once the file is downloaded in order to save it in the Files app you will need to use UIActivityViewController.
func presentActivityViewController(withUrl url: URL) {
DispatchQueue.main.async {
let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
activityViewController.popoverPresentationController?.sourceView = self.view
self.present(activityViewController, animated: true, completion: nil)
}
}
I haven't tested it but my assumption is that you get this error because your localURL variable is not a filesystem URL.
Instead of using URL(string: String) you should be using URL(fileURLWithPath: String) when opening files.

get the URL for NSData saved in CoreData

I'm saving a UIImage to Core Data. So first, I convert it to NSData, then save it.
I need to get the URL for the image after it's saved. I'm doing this because I want to schedule a local notification with an attachment, and the only way to do it, AFAIK, is to with a URL.
Here is my code:
//my image:
var myImage: UIImage?
var imageData: NSData?
if let image = myImage {
imageData = UIImageJPEGRepresentation(image, 0.5)! as NSData
}
myEntity.setValue(imageData, forKey: "image")
And that's how I should add an attachment to the notification:
UNNotificationAttachment.init(identifier: String, url: URL>, options: [AnyHashable : Any]?)
I'm saving the image and scheduling the notification manually when the user taps on a button to save the image.
Please let me know if you need extra info.
You can't get the URL. If you configured this property to use external storage then yes, technically there could be a file URL. Maybe. But there's no documented way to get it, and anyway it might not exist after all-- because the external storage setting doesn't require Core Data to use external storage, it just allows it to do so.
If you didn't use that setting then there's never any URL since the image is saved as part of the SQLIte file.
If you need a file URL for the image, save the image to a file separately from Core Data and save the file name as an entity property. Then the file URL is wherever you saved the file.
And an implementation of how I saved it and then got the URL in practice when I had the same challenge:
Swift 5:
func getImageURL(for image: UIImage?) -> URL {
let documentsDirectoryPath:NSString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString
let tempImageName = "tempImage.jpg"
var imageURL: URL?
if let image = image {
let imageData:Data = image.jpegData(compressionQuality: 1.0)!
let path:String = documentsDirectoryPath.appendingPathComponent(tempImageName)
try? image.jpegData(compressionQuality: 1.0)!.write(to: URL(fileURLWithPath: path), options: [.atomic])
imageURL = URL(fileURLWithPath: path)
try? imageData.write(to: imageURL!, options: [.atomic])
}
return imageURL!
}

NSData - Write to temporary file / directory

I am a little lost on my quest to -
Download a CKAsset (PDF File)
Assign a Temporary Filename
Write the contents of the CKAsset to the filename
I have managed to download the CKAsset and display the contents in a UIWebView, however I am stumbling over steps 2 and 3, I have a filename from a String, and despite trying a variety of WriteToFile combinations I receive errors.
My code is thus :
let filename = record.object(forKey: "materialsFilename")
if materialsType == "PDF" || materialsType == "pdf" {
if let asset1 = record.object(forKey: "materialsFile") as? CKAsset {
let doc1Data : NSData? = NSData(contentsOf:asset1.fileURL)
let path = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(filename as! String)
let contentsOfFile = doc1Data
var error: NSError?
// Write File
if contentsOfFile.writeToFile(path, atomically: true, encoding: String.Encoding.utf8, error: &error) == false {
if let errorMessage = error {
print("Failed to create file")
print("\(errorMessage)")
}
} else {
print("File \(filename) created at tmp directory")
}
This version presents the error -
Cannot invoke 'writeToFile' with an argument list of type '(URL?,
atomically: Bool, encoding: String.Encoding, error: inout NSError?)'
The temporary file once created will be passed to a UIActivityViewController, to print / email / airdrop the PDF, having only a CKAsset name, the the UIActivityViewController cannot associate the file type to any of the users installed apps, save for print.
After a little head scratching and reviewing my choices following the pointers above, I changed tack and didn't really need to write to a file, just rename the CKAsset, which I achieved with the following script -
let materialsType = record.object(forKey: "materialsType") as! String
let filename = record.object(forKey: "materialsFilename") as! String
if materialsType == "PDF" || materialsType == "pdf" {
if let asset1 = record.object(forKey: "materialsFile") as? CKAsset {
let doc1Data : Data? = Data(contentsOf:asset1.fileURL) as Data?
let fileURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(filename)
self.materialsWebView.load(doc1Data! as Data, mimeType: "application/pdf", textEncodingName: "UTF-8", baseURL: NSURL() as URL)
self.filenameURL = [(fileURL)]
}
The key seemed to hinge on two lines -
let fileURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(filename)
and
self.filenameURL = [(fileURL)]
Which generates the filename for the UIActivityViewController and thus opens up the access to a number of additional Apps.
Are you sure you're using the correct writeToFile method? The NSData reference doesn't show a method with the same signature you're using. Try using one of the ones listed in the reference.
I only find the following function in Data class.
func write(to: URL, options: Data.WritingOptions)
Try this.

How to get URL from UIImage?

I have an iOS app in which there are 2 ways the user can get a picture:
Select it from photos library (UIImagePickerController)
Click it from a custom made camera
Here is my code for clicking the image from a custom camera (this is within a custom class called Camera, which is a subclass of UIView)
func clickPicture(completion:#escaping (UIImage) -> Void) {
guard let videoConnection = stillImageOutput?.connection(withMediaType: AVMediaTypeVideo) else { return }
videoConnection.videoOrientation = .portrait
stillImageOutput?.captureStillImageAsynchronously(from: videoConnection, completionHandler: { (sampleBuffer, error) -> Void in
guard let buffer = sampleBuffer else { return }
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer)
let dataProvider = CGDataProvider(data: imageData! as CFData)
let cgImageRef = CGImage(jpegDataProviderSource: dataProvider!, decode: nil, shouldInterpolate: true, intent: .defaultIntent)
let image = UIImage(cgImage: cgImageRef!, scale: 1, orientation: .right)
completion(image)
})
}
Here is how I click the image within the ViewController:
#IBAction func clickImage(_ sender: AnyObject) {
cameraView.clickPicture { (image) in
//use "image" variable
}
}
Later, I attempt to upload this picture to the user's iCloud account using CloudKit. However I receive an error saying the record is too large. I then came across this SO post, which says to use a CKAsset. However, the only constructor for a CKAsset requires a URL.
Is there a generic way I can get a URL from any UIImage? Otherwise, how can get a URL from the image I clicked using my custom camera (I have seen other posts about getting a url from a UIImagePickerController)? Thanks!
CKAsset represents some external file (image, video, binary data and etc). This is why it requires URL as init parameter.
In your case I would recommend to use following steps to upload large image to CloudKit:
Save UIImage to local storage (e.g. documents directory).
Initialize CKAsset with path to image in local storage.
Upload asset to Cloud.
Delete image from local storage when uploading completed.
Here is some code:
// Save image.
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let filePath = "\(path)/MyImageName.jpg"
UIImageJPEGRepresentation(image, 1)!.writeToFile(filePath, atomically: true)
let asset = CKAsset(fileURL: NSURL(fileURLWithPath: filePath)!)
// Upload asset here.
// Delete image.
do {
try FileManager.default.removeItem(atPath: filePath)
} catch {
print(error)
}

Save incoming PDF in WebView to Memory

I have a project that is associated with opening PDF files. This is set in the Info.plist. When I get a PDF attachment in email, I can hold my finger on the PDF attachment and then 'Open in' in my app. In my AppDelegate, I have the following added:
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
incomingTransfer = URL
return true
}
incomingTransfer is a Global Variable declared in another ViewController as an NSURL. This ViewController also has a UIWebView and the incomingTransfer loads into it and I'm able to see the new PDF file. My goal is to have a button that allows the user to save the incoming PDF as a PDF. I'm having trouble with this. I thought I had it all figured out, but it wasn't saving as a PDF at all, but rather as a String. Can someone help me please? My goal is to save the incoming PDF file as a PDF to the app memory, preferably in DocumentDirectory. I have a hard time trying to convert Objective C to Swift. My original code to save it was:
let html = String(incomingFileTransfer)
let fmt = UIMarkupTextPrintFormatter(markupText: html)
let render = UIPrintPageRenderer()
render.addPrintFormatter(fmt, startingAtPageAtIndex: 0)
let page = CGRect(x: 0, y: 0, width: 595.2, height: 841.8) // A4, 72 dpi
let printable = CGRectInset(page, 0, 0)
render.setValue(NSValue(CGRect: page), forKey: "paperRect")
render.setValue(NSValue(CGRect: printable), forKey: "printableRect")
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, CGRectZero, nil)
for i in 1...render.numberOfPages() {
UIGraphicsBeginPDFPage();
let bounds = UIGraphicsGetPDFContextBounds()
render.drawPageAtIndex(i - 1, inRect: bounds)
}
UIGraphicsEndPDFContext();
recipeFileName = fileName.text!
print("File Name Entered: \(recipeFileName)")
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
pdfData.writeToFile("\(documentsPath)/\(recipeFileName).pdf", atomically: true)
I figured it out. I created a class called 'PDFFile'. Within 'PDFFile' are two variables, named var name: String and var url: NSURL. Within my 'IncomingTransfer' ViewController, I have the 'save' button create and save the new file with a typed name from the UITextField and the incomingURL specified in my AppDelegate is assigned to the url variable. Both are then saved to the PDFFile class using NSCoding. I then set a UITableView for it's dataSource from the PDFFile Class array data. I created a segue when the user clicks on the UITableViewCell and that goes to a new ViewController with a UIWebView. This WebView loads the PDF from a urlRequest using the specified url variable, saved from the NSCoding.
AppDelegate Code:
// Allows incoming file access (PDF)
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
// Transfer incoming file to global variable to be read
if url != "" {
// Load from Mail App
incomingFileTransfer = url
incomingStatus = "Incoming"
} else {
// Regular Load
print("App Delegate: No incoming file")
incomingFileTransfer = nil
}
return true
}
IncomingFile code and save button code:
// MARK: Properties
var file: PDFFile?
// MARK: Actions
// Save Button
let name = fileName.text ?? ""
let url = incomingFileTransfer
file = PDFFile(name: name, url: url)
// MARK: NSCoding
func saveFiles() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(pdfFiles, toFile: PDFFile.ArchiveURL.path!)
if !isSuccessfulSave {
print("Failed to save PDF file")
}
}
Viewing Saved Incoming PDF Later ViewController code:
// MARK: Properties
#IBOutlet weak var pdfItemWebView: UIWebview!
var incomingURL: NSURL!
// Within ViewDidLoad
if let file = file {
pdfFileName.text = file.name
incomingURL = file.url
print("Saved URL: \(incomingURL)")
print("Pending load...")
let request = NSURLRequest(URL: incomingURL!)
pdfItemWebView.loadRequest(request)
}
It was complicated but worked for me. There may be an easier way, but this is what I figured out and its works for my needs.

Resources