I have a GitHub repository that allows the user to save a paginated PDF on Mac from some HTML by loading it into a WebView and using an NSPrintOperation (specifically, an NSPrintSaveJob with showsPrintPanel set to false) lets the user save that PDF to any location on their Mac with a save panel similar to the default NSSavePanel. However, I'm experimenting with the code and I'd like to instead save the created PDF to a particular folder (/Users/owlswipe/Downloads/) without the save panel.
My code to save a PDF from a WebView (with a save panel) is currently this:
let printOpts: [String : AnyObject] = [NSPrintJobDisposition:NSPrintSaveJob as AnyObject]
let printInfo: NSPrintInfo = NSPrintInfo(dictionary: printOpts)
printInfo.paperSize = NSMakeSize(595.22, 841.85)
let printOp: NSPrintOperation = NSPrintOperation(view: webView.mainFrame.frameView.documentView, printInfo: printInfo)
printOp.showsPrintPanel = false
printOp.showsProgressPanel = false
printOp.run()
How can I adapt that code to save the PDF to a preset folder instead of to the user's choice of folders from a save panel?
This piece of code is working for me in Swift 4/Cocoa to accomplish what you want, however there's a bit more code in there as it is rendering the contents of a WKWebView into 8.5" x 11" pages for a PDF.
So for your app, there would be appropriate adjustments for your content stream / print object, but the configuration of the print operation will be the same to get the "no dialog" results you're wanting.
However, to test it out you could just dump your string into the webview and use the code as is. Generated files appear in the root of your "user/documents" directory.
static func createPDF(htmlString: String, streamId: String = "someStream") {
let webView = WebView()
webView.mainFrame.loadHTMLString(htmlString, baseURL: nil)
let when = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: when) {
let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let directoryURLStr = directoryURL.absoluteString+"\(streamId).pdf"
let outputFilePath = URL(string: directoryURLStr)
let printOpts: [NSPrintInfo.AttributeKey : Any] = [
NSPrintInfo.AttributeKey.jobDisposition : NSPrintInfo.JobDisposition.save,
NSPrintInfo.AttributeKey.jobSavingURL : outputFilePath
]
let printInfo: NSPrintInfo = NSPrintInfo(dictionary: printOpts)
let baseMargin: CGFloat = 9.0; // .125"
printInfo.paperSize = NSMakeSize(612, 792); // 8.5" x 11/2"
printInfo.topMargin = baseMargin
printInfo.leftMargin = baseMargin
printInfo.rightMargin = baseMargin
printInfo.bottomMargin = baseMargin
let printOp: NSPrintOperation = NSPrintOperation(view: webView.mainFrame.frameView.documentView, printInfo: printInfo)
printOp.showsPrintPanel = false
printOp.showsProgressPanel = false
printOp.run()
Swift.print("Document complete: \(outputFilePath!.absoluteString)")
}
}
Related
I'm using the following code to draw a text over PDF page.Everything works fine, but if you process a PDF with internal links eg: a Book with Content links to pages, the resultant PDF file seems to have the links stripped off.Why does this happen?
let data = NSMutableData()
let consumer = CGDataConsumer(data: data as CFMutableData)!
let context = CGContext(consumer: consumer, mediaBox: nil, nil)!
let pdffile=PDFDocument(url: input)
for y in stride(from: 0, to: pagecount, by: 1)
{
let page: PDFPage = pdffile!.page(at: y)!
var mediaBox = page.bounds(for: PDFDisplayBox.mediaBox)
NSGraphicsContext.current = NSGraphicsContext(cgContext: context, flipped: false)
let bounds = page.bounds(for: PDFDisplayBox.mediaBox)
let size = bounds.size
page.draw(with: .mediaBox, to: context)
text.draw(in:drawrect,withAttributes:textFontAttributes);
context.endPDFPage()
}
context.closePDF()
try! data!.write(to: GetOutputFileName(inputfile: x), options[.atomicWrite])
Links are just kind of annotations, so we need just re-add annotations from original pages to new pages, like
...
}
context.closePDF()
let anotherDocument = PDFDocument(data:data as Data)
for y in stride(from: 0, to: pdffile!.pageCount, by: 1)
{
let page: PDFPage = pdffile!.page(at: y)!
let newPage: PDFPage = anotherDocument!.page(at: y)!
for anotatation in page.annotations {
newPage.addAnnotation(anotatation) // << here !!
}
}
Tested with Xcode 13.4 / macOS 12.5
Test module is here
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
I am using the Google Maps SDK for iOS - https://developers.google.com/maps/documentation/ios-sdk/marker#use_the_markers_icon_property
Combined with the Maps SDK for iOS Utility Library https://developers.google.com/maps/documentation/ios-sdk/utility/kml-geojson#render-kml-data
I am trying to use the utility library to render a kml file on a map. It mostly works, however the custom icons for the markers are not loading. The markers with their titles, snippets, and locations all load correctly. The only thing that does not work is the custom icon for the marker.
Originally, I thought it was an issue with the utility library, so I spent some time trying to write my own code to go through the kml file and add the custom markers myself. However, before I got too far I noticed that even when I try to add a basic marker with a custom icon, I cannot. This led me to believe it was an issue not with the utility library but with the Maps SDK for iOS. I've tried moving the folder that the image is in, and making sure that the code can see the path to the images, but I cannot get it to work.
This is the code that I have in my project
let path = Bundle.main.path(forResource: testFile, ofType: "kml")
let url = URL(fileURLWithPath: path!)
let kmlParser = GMUKMLParser(url: url)
kmlParser.parse()
let camera = GMSCameraPosition.camera(withLatitude: lat, longitude: long, zoom: zoom)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.mapType = GMSMapViewType.terrain
mapView.isMyLocationEnabled = true
mapView.settings.zoomGestures = true
mapView.settings.myLocationButton = true
let renderer = GMUGeometryRenderer(map: mapView, geometries: kmlParser.placemarks, styles: kmlParser.styles, styleMaps: kmlParser.styleMaps)
renderer.render()
This also does not work
let position = CLLocationCoordinate2D(latitude: lat, longitude: long)
let marker = GMSMarker(position: position)
marker.title = "Test"
marker.icon = UIImage(named: "icon-1")
marker.map = mapView
Thanks in advance for any help
I haven't figured out why the utils library wasn't working, but I did come up with my own fix. It's horrible, but I can come back and make it better later after we've finished adding all the other necessary features to the app and can focus on cleaning up the code .
First, I made a new array of placemarks that had everything except the map markers. I then used this array of placemarks instead of kmlParser.placemarks, so that everything else could be added by the utility library.
//Removing markers without icons
var myIndex = 0
var removed = [GMUGeometryContainer]()
for mark in kmlParser.placemarks{
if(mark.geometry.type != "Point"){
removed.append(kmlParser.placemarks[myIndex])
}
myIndex += 1
}
let renderer = GMUGeometryRenderer(map: mapView, geometries: removed, styles: kmlParser.styles, styleMaps: kmlParser.styleMaps)
renderer.render()
After that, I made my own horrible horrible method that reads the kml file again, and only picks out the placemarks and styles for them and returns an array of Markers.
func addMarkers(fileName:String) -> [GMSMarker]{
var markers = [GMSMarker]()
if let path = Bundle.main.path(forResource: fileName, ofType: "kml"){
do{
let data = try String(contentsOfFile: path, encoding: .utf8)
let myStrings = data.components(separatedBy: .newlines)
var styleToIcon = [String: String]()
var lineNum = 0
for line in myStrings{
//Detecting new style that will be used in placemarks
if line.contains("Style id") && line.contains("normal") && !line.contains("line-"){
let newKey = String(line.split(separator: "\"")[1])
let newValue = String(myStrings[lineNum+4].split(separator: ">")[1].split(separator: "/")[1].split(separator: "<")[0])
styleToIcon[newKey] = newValue
}
//Detecting new placemark on map
else if(line.contains("<Placemark>") && !myStrings[lineNum+2].contains("#line")){
//Get name
var name = myStrings[lineNum+1].split(separator: ">")[1].split(separator: "<")[0]
//Sometimes name has weird CDATA field in it that needs to be removed
if(name.contains("![CDATA")){
name = name.split(separator: "[")[2].split(separator: "]")[0]
}
//Get snippet (description)
var snippet = myStrings[lineNum+2].split(separator: ">")[1].split(separator: "<")[0]
//Sometimes snippet has weird CDATA field in it that needs to be removed
if(snippet.contains("![CDATA")){
snippet = snippet.split(separator: "[")[2].split(separator: "]")[0]
}
//Get style
let style = String(myStrings[lineNum+3].split(separator: ">")[1].split(separator: "#")[0].split(separator: "<")[0] + "-normal")
//Get Coordinates
let coordStringSplit = myStrings[lineNum+6].split(separator: ",")
var lat = 0.0
var long = 0.0
if(coordStringSplit[0].contains("-")){
long = Double(coordStringSplit[0].split(separator: "-")[1])! * -1.0
}else{
long = Double(coordStringSplit[0])!
}
if(coordStringSplit[1].contains("-")){
lat = Double(coordStringSplit[1].split(separator: "-")[1])! * -1.0
}else{
lat = Double(coordStringSplit[1])!
}
//Create marker and add to list of markers
let position = CLLocationCoordinate2D(latitude: lat, longitude: long)
let marker = GMSMarker(position: position)
marker.title = String(name)
marker.snippet = String(snippet)
marker.icon = UIImage(named: styleToIcon[style]!)
markers.append(marker)
}
lineNum += 1
}
}catch{
print(error)
}
}
return markers
}
This is so heavily related to how my kml files look that I doubt it will help anyone else, but I thought I should post it just in case.
Now that we have that method, all we need to do is go back to where we were rendering all of the kml data and render those markers on the map
//Adding markers with icons
let newMarkers = addMarkers(fileName: courseName)
for mark in newMarkers{
mark.map = mapView
}
I also had to go through my kml files manually and fix some of the image names, but that wasn't a big deal. Even if the utility library worked I would need to do that because the utility library only does kml files and not kmz, so each kml file references the same folder for images and uses the same names for images. It's fine, only takes a few minutes per file. Would be nice if there was a kmz library but oh well.
Hopefully this helps someone else, and hopefully I can find the real solution soon (unless its a problem with the utility library in which case hopefully it's fixed soon).
//call method by passing ;
if userLocation.coordinate.latitude != 0.0 && userLocation.coordinate.longitude != 0.0
{
self.updateCurrentPositionMarker(currentLocation: CLLocation(latitude: userLocation.coordinate.latitude, longitude:userLocation.coordinate.longitude))
}
//methods
func updateCurrentPositionMarker(currentLocation: CLLocation) {
self.currentPositionMarker.map = nil
self.currentPositionMarker = GMSMarker(position: currentLocation.coordinate)
if self.imageDataUrl != ""
{
let camera: GMSCameraPosition = GMSCameraPosition.camera(withLatitude: currentLocation.coordinate.latitude, longitude: currentLocation.coordinate.longitude, zoom: 18.0)
self.mapView.camera = camera
//self.imageDataUrl == image to show
self.currentPositionMarker.iconView = self.drawImageWithProfilePic(urlString:self.imageDataUrl,image: UIImage.init(named: “backgroungImage”)!)
self.currentPositionMarker.zIndex = 1
}
self.currentPositionMarker.map = self.mapView
self.mapView.reloadInputViews()
}
func drawImageWithProfilePic(urlString:String, image: UIImage) -> UIImageView {
let imgView = UIImageView(image: image)
imgView.frame = CGRect(x: 0, y: 0, width: 90, height: 90)
let picImgView = UIImageView()
picImgView.sd_setImage(with:URL(string: urlString))
picImgView.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
imgView.addSubview(picImgView)
picImgView.center.x = imgView.center.x
picImgView.center.y = imgView.center.y-10
picImgView.layer.cornerRadius = picImgView.frame.width/2
picImgView.clipsToBounds = true
imgView.setNeedsLayout()
picImgView.setNeedsLayout()
// let newImage = imageWithView(view: imgView)
// return newImage
return imgView
}
I am building a form style app that allows the users to fill in textfields. once they fill up the page it will continue onto a second view. This will be about 5 pages worth of patient forms. when they get to the last page I want to have a print button that prints all 5 or 6 pages they have just filled out. I am trying to use the AirPrint code that I have recently found on here, but it will only let me do the current view.
extension UIView {
func toImage() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale)
// drawHierarchy(in: self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
let pInfo = UIPrintInfo(dictionary:nil)
pInfo.outputType = UIPrintInfoOutputType.general
pInfo.jobName = "Print Patient Forms"
//Set up print controller
let printController = UIPrintInteractionController.shared
printController.printInfo = pInfo
//Assign a UIMage version of my UIView as a printing Item
printController.printingItem = self.view
var pInfo : UIPrintInfo = UIPrintInfo.printInfo()
pInfo.orientation = UIPrintInfoOrientation.portrait
let formatter = UIMarkupTextPrintFormatter(markupText: textView.text!)
printController.showsNumberOfCopies = false
printController.showsPageRange = true
printController.printFormatter = formatter printController.present(animated: true, completionHandler: nil)
How do I go about letting this print all the pages?
I have the following lines of code to print the webView content.
let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let printOpts: NSDictionary = [NSPrintJobDisposition: NSPrintSaveJob, NSPrintSaveJob: directoryURL, NSPrintScalingFactor: 2.0]
let printInfo = NSPrintInfo(dictionary: printOpts as! [String : AnyObject])
printInfo.horizontalPagination = .autoPagination
printInfo.verticalPagination = .autoPagination
let printOperation = NSPrintOperation(view: webView.mainFrame.frameView, printInfo: printInfo)
printOperation.printPanel.options = [.showsOrientation, .showsPaperSize, .showsPreview, .showsPageSetupAccessory, .showsScaling, .showsPrintSelection]
printOperation.run()
The problem is that it sometimes causes the application to crash when the user interacts with the print operation panel.
If I just click on the cancel button, the application can crash. And if I try to set a different scale rate, it can sometimes crash. I don't know why the application will crash. It's always the last line. What am I doing wrong? Thanks.
There's a bug in the print panel code when .showsCopies is not in the options. It has a use-after-free bug with the Copies-related subviews. It removes them from the view hierarchy, allowing them to be deallocated. However, it keeps a dangling reference to the text field and tries to operate on it in various circumstances. For example, it sometimes tries to make it the first responder.
You basically should just never try to suppress the Copies field.
my code for print a pdf from URL for OS X:
let printInfo = NSPrintInfo.shared
let manager = FileManager.default
do{
let directoryURL = try manager.url(for: .documentDirectory, in:.userDomainMask, appropriateFor:nil, create:true)
let docURL = NSURL(string:"XX.pdf", relativeTo:directoryURL)
let pdfDoc = PDFDocument.init(url: docURL! as URL)
let page = CGRect(x: 0, y: 0, width: 595.2, height: 1841.8) // A4, 72 dpi
let pdfView : PDFView = PDFView.init(frame: page)
pdfView.document = pdfDoc
let operation: NSPrintOperation = NSPrintOperation(view: pdfView, printInfo: printInfo)
operation.printPanel.options.insert(NSPrintPanel.Options.showsPaperSize)
operation.printPanel.options.insert(NSPrintPanel.Options.showsOrientation)
operation.run()
}catch{
}