change path for exported pdf - swift

I have found this code over the internet for exporting the view to PDF. But the downloaded file stores in the library which will be hard to locate. How can I change the path, so that when I use the app on my iPhone, it gets stored in the download folder. Also, the pdf looks like it is a screenshot of the view, is there any way I can control the view to crop such as I don't want the navigation bar and other button to appear on the screen. Is there any way I can make it look like a document rather than a screenshot. Or may be resize the view to the point where I want. Or does anyone have a better code to export the file to PDF?
func exportToPDF() {
let outputFileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("SwiftUI.pdf")
let pageSize = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
let rootVC = UIApplication.shared.windows.first?.rootViewController
//Render the PDF
let pdfRenderer = UIGraphicsPDFRenderer(bounds: CGRect(origin: .zero, size: pageSize))
DispatchQueue.main.async {
do {
try pdfRenderer.writePDF(to: outputFileURL, withActions: { (context) in
context.beginPage()
rootVC?.view.layer.render(in: context.cgContext)
})
print("wrote file to: \(outputFileURL.path)")
} catch {
print("Could not create PDF file: \(error.localizedDescription)")
}
}
}

Related

Swift: Loss of resolution when redrawing PDF

I'm trying to use a PDF file (contains a form with table for several 'records') as a template.
My idea is to read in the original file as a template, and then use Swifts drawing features to insert the text at the relevant positions before saving as a new pdf file and printing.
My problem is that i'm seeing a small loss of resolution (fonts are slightly wooly, gridlines are no longer crisp) when re-saving the output.
I've tried two approaches, the first with my 'template' as a file in the project, and the second with it as an Asset (Scales: Single Scale, Resizing: Preserve Vector Data).
Here is my code:
func createCompletedForm(records: [MyDataObject]) -> URL? {
let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = URL(fileURLWithPath: "pdfToPrint", relativeTo: directoryURL).appendingPathExtension("pdf")
guard let templateUrl = Bundle.main.url(forResource: "MyPdfTemplate", withExtension: "pdf") else { return nil }
guard let document = CGPDFDocument(templateUrl as CFURL) else { return nil }
guard let page = document.page(at: 1) else { return nil }
let pageTemplate = page.getBoxRect(.mediaBox)
UIGraphicsBeginPDFContextToFile(fileURL.path, pageTemplate, nil)
guard let pdfContext = UIGraphicsGetCurrentContext() else {
print("Unable to access PDF Context.")
return nil
}
// Mark the beginning of the page.
pdfContext.beginPDFPage(nil)
// Save the context state to restore after we are done drawing the image.
pdfContext.saveGState()
// Change the PDF context to match the UIKit coordinate system.
pdfContext.translateBy(x: 0, y: StandardPageDimensions.ISO216_A4.height)
pdfContext.scaleBy(x: 1, y: -1)
// Option 1: Draw PDF from a file added to my project
DrawingHelper.drawPDFfromCGPDF(page: page, drawingArea: pageTemplate)
// Option 2: Draw PDF from Assets
//let baseTemplate = UIImage(named: "MyPdfTemplate")
//baseTemplate?.draw(at: CGPoint(x: 0, y: 0))
// Draw the records over the template - NOT the source of the problem, happens even when commented out
//addRecordsToTemplate(records: records)
// Restoring the context back to its original state.
pdfContext.restoreGState()
// Mark the end of the current page.
pdfContext.endPDFPage()
UIGraphicsEndPDFContext()
// Useful to find and open the file produced on the simulator
print("pdf created at : \(fileURL.path)")
return fileURL
}
// And the drawing function from my helper class
static func drawPDFfromCGPDF(page: CGPDFPage, drawingArea: CGRect) {
let renderer = UIGraphicsImageRenderer(size: drawingArea.size)
let img = renderer.image { ctx in
UIColor.white.set()
ctx.fill(drawingArea)
ctx.cgContext.translateBy(x: 0.0, y: drawingArea.size.height)
ctx.cgContext.scaleBy(x: 1.0, y: -1.0)
ctx.cgContext.drawPDFPage(page)
}
img.draw(at: CGPoint(x: 0, y: 0))
}

How to name UIImage drawn from UIBezierPath() programmatically in Xcode-Swift?

How do we name an image programmatically. For example, assign a name to the image generated below. A name that we can use to distinguish the image from other images drawn programmatically.
func drawOval (width: CGFloat, height: CGFloat, name: String) -> UIImage {
let renderer = UIGraphicsImageRenderer(size: CGSize(width: width, height: height))
let image = renderer.image { ctx in
let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: width, height: height))
path.stroke()
}
// TO DO: Assign this image a name, for example "image01"
return image
}
You can use tags on each UIImageView. I’m not aware of a way to add an identifier to a UIImage directly since it is a subclass of NSObject and not UIView. In order to add a tag to an object in Swift, the object must be a view of some kind.
To implement this, you would keep a variable outside of that function that keeps track of the current tag, then increment it in your function. For example:
var currentTag = 0
//Function now returns a UIImageView
func drawOval (width: CGFloat, height: CGFloat, name: String) -> UIImageView {
let renderer = UIGraphicsImageRenderer(size: CGSize(width: width, height: height))
let image = renderer.image { ctx in
let path = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: width, height: height))
path.stroke()
}
let imageView = UIImageView(image: image)
imageView.tag = currentTag
currentTag += 1
return imageView
}
Then later in your code:
if (imageView.tag == 0) {
//Do something
}
//You can also use
let taggedImageView = viewWithTag(0)
EDIT: If you want to save the images and load them via one of the available UIImage initializers, you can write them to a cache folder on disk, then retrieve them using UIImage(pathToFile:):
//This will store the images in the caches directory for your app, which
//the system can clear when the device is low on storage. It will not be
//cleared while your app is open, though.
func saveImageToCacheDynamically(image: UIImage, name: String) {
let paths = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
let localPath = paths[0].appendingPathComponent(“ImageCache”, isDirectory: true)
do {
if FileManager.default.fileExists(atPath: localPath.absoluteString) {
//Write the png data representation of the image to disk in plaintext format
try image.pngData().write(to: localPath.appendingPathComponent("\(name).txt"))
} else {
try FileManager.default.createDirectory(at: localPath, withIntermediateDirectories: true, attributes: nil)
//Write the png data representation of the image to disk in plaintext format
try image.pngData().write(to: localPath.appendingPathComponent("\(name).txt"))
}
} catch {
print("Error locally saving image: \(error.localizedDescription)")
}
//Later in your code...
let paths = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
let localPath = paths[0].appendingPathComponent("ImageCache/\(imageIdentifier).txt")
if FileManager.default.fileExists(atPath: localPath.absoluteString) {
var fileData: Data!
do {
try fileData = Data(contentsOf: localPath)
} catch {
print("Error reading image file: \(error.localizedDescription)")
}
let image = UIImage(data: fileData)
//Do something with image
} else {
print("Error: image does not exist")
}
I believe what you are looking for is something like NSCache...
You can define a cache by something similar to this:
let imageCache = NSCache<String, UIImage>()
Then you can add objects to the cache like this, where someKeyString is the 'name' you are referring to:
imageCache.setObject(someImage, forKey: someKeyString)
And then finally you can retrieve images from the cache like
imageCache.object(forKey: someKeyString)
I would recommend using extensions or something similar to maintain a reference to your cache everywhere in your app.
** NOTE:
NSCaches are cleared when memory space is short, your app closes, etc. See here
For more permanent storage, I would recommend using UserDefaults, which Apple describes as "An interface to the user’s defaults database, where you store key-value pairs persistently across launches of your app." Use this for things like profile images or things that won't change very often. I would also recommend looking into Core Data

How to Save PDF (and print) from UITableView (or cell) easy way?

I read and search a lot on internet and stack, but I have a problem
I've used this extension
extension UITableView {
// Export pdf from UITableView and save pdf in drectory and return pdf file path
func exportAsPdfFromTable() -> String {
self.showsVerticalScrollIndicator = false
let originalBounds = self.bounds
self.bounds = CGRect(x:originalBounds.origin.x, y: originalBounds.origin.y, width: self.contentSize.width, height: self.contentSize.height)
let pdfPageFrame = CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.contentSize.height)
let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, pdfPageFrame, nil)
UIGraphicsBeginPDFPageWithInfo(pdfPageFrame, nil)
guard let pdfContext = UIGraphicsGetCurrentContext() else { return "" }
self.layer.render(in: pdfContext)
UIGraphicsEndPDFContext()
self.bounds = originalBounds
// Save pdf data
return self.saveTablePdf(data: pdfData)
}
// Save pdf file in document directory
func saveTablePdf(data: NSMutableData) -> String {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let docDirectoryPath = paths[0]
let pdfPath = docDirectoryPath.appendingPathComponent("myPDF.pdf")
if data.write(to: pdfPath, atomically: true) {
return pdfPath.path
} else {
return ""
}
}
}
Then i save the path in this way
myPdfPath = self.tableView.exportAsPdfFromTable()
I tried several ways to show the pdf:
- UIActivityViewController (with this work but not well)
- WKWebView
- SFSafariViewController (only http / https, path file not work)
- UIDocumentInteractionController (not show preview and not work)
The extension used to save the pdf does not work well (not fill print preview page). Generate a 125MB pdf with a title I don't want.
Is there an easy way to do this?
1) TableView (generate pdf on a white background (always even if the tableview is red for example)
2) Print the generated pdf.
Thanks

IMessage MSSticker view created from UIView incorrect sizing

Hey I have been struggling with this for a couple of days now and can't seem to find any documentation out side of the standard grid views for MSStickerView sizes
I am working on an app that creates MSStickerViews dynamically - it does this via converting a UIView into an UIImage saving this to disk then passing the URL to MSSticker before creating the MSStickerView the frame of this is then set to the size of the original view.
The problem I have is that when I drag the MSStickerView into the messages window, the MSStickerView shrinks while being dragged - then when dropped in the messages window, changes to a larger size. I have no idea how to control the size when dragged or the final image size
Heres my code to create an image from a view
extension UIView {
func imageFromView() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
defer { UIGraphicsEndImageContext() }
if let context = UIGraphicsGetCurrentContext() {
self.layer.render(in: context)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image
}
return nil
}
}
And here's the code to save this to disk
extension UIImage {
func savedPath(name: String) -> URL{
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let filePath = "\(paths[0])/name.png"
let url = URL(fileURLWithPath: filePath)
// Save image.
if let data = self.pngData() {
do {
try data.write(to: url)
} catch let error as NSError {
}
}
return url
}
}
finally here is the code that converts the data path to a Sticker
if let stickerImage = backgroundBox.imageFromView() {
let url = stickerImage.savedPath(name: textBox.text ?? "StickerMCSticker")
if let msSticker = try? MSSticker(contentsOfFileURL: url, localizedDescription: "") {
var newFrame = self.backgroundBox.frame
newFrame.size.width = newFrame.size.width
newFrame.size.height = newFrame.size.height
let stickerView = MSStickerView(frame: newFrame, sticker: msSticker)
self.view.addSubview(stickerView)
print("** sticker frame \(stickerView.frame)")
self.sticker = stickerView
}
}
I wondered first off if there was something I need to do regarding retina sizes, but adding #2x in the file just breaks the image - so am stuck on this - the WWDC sessions seem to show stickers being created from file paths and not altering in size in the transition between drag and drop - any help would be appreciated!
I fixed this issue eventually by getting the frame from the view I was copying's frame then calling sizeToFit()-
init(sticker: MSSticker, size: CGSize) {
let stickerFrame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
self.sticker = MSStickerView(frame: stickerFrame, sticker: sticker)
self.sticker.sizeToFit()
super.init(nibName: nil, bund
as the StickerView was not setting the correct size. Essentially the experience I was seeing was that the sticker size on my view was not accurate with the size of the MSSticker - so the moment the drag was initialized, the real sticker size was implemented (which was different to the frame size / autoLayout I was applying in my view)

How do I write image to file?

I am using Swift 3, Xcode 8.2, iOS 10
For debugging purposes, I am trying to take an image, crop it and show what the cropped image looks like. I am trying to write this image to my computer (Mac OS X) desktop but all the tutorials I've found has me writing this to my documents directory which I incorrectly thought would actually be a directory on my computer but turns out it's this location on the phone itself that I can't figure out how to access.
I have this so far:
let finalImage : UIImage
let crop_section = CGRect(x: 0.0, y: 0.0, width: 1000.0, height: 1000.0)
let cg_image = screenshot.cgImage?.cropping(to: crop_section)
finalImage = UIImage(cgImage: cg_image!)
let documentsDirectoryURL = try! FileManager().url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
// create a name for your image
print(documentsDirectoryURL)
let fileURL = documentsDirectoryURL.appendingPathComponent("cropped.png")
if !FileManager.default.fileExists(atPath: fileURL.path) {
do {
try UIImagePNGRepresentation(finalImage)!.write(to: fileURL)
print("Image Added Successfully")
} catch {
print(error)
}
} else {
print("Image Not Added")
}
I want to replace documentDirectory with something like ../Desktop/cropped.png but I can't figure out how to do this. Any help would be greatly appreciated!
I want to replace documentDirectory with something like ../Desktop/cropped.png
You can't. This is an iOS program. An iOS app is sandboxed within its own set of directories on the iOS device / simulator; it cannot reach out and see the Mac OS X desktop. They are two different universes.