How does one print from a SwiftUI app (on macOS)? Is there any SwiftUI or Swift API for this, or do I just use the old AppKit APIs (https://developer.apple.com/documentation/appkit/printing)? What if I wanted to print from iOS?
Printing from a SwiftUI app (on macOS)?
Hi @JetForMe ,
First, use ImageRenderer to create an image from your SwiftUI view.
Example code snippet:
import SwiftUI
import AppKit
import PDFKit
struct ContentView: View {
@State private var savedPDF: PDFDocument?
var body: some View {
let trophyAndDate = createAwardView()
VStack {
trophyAndDate
Button("Save Achievement") {
guard let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let renderedUrl = documentDirectory.appending(path: "linechart.pdf")
let renderer = ImageRenderer(content: trophyAndDate)
renderer.render { size, renderer in
var mediaBox = CGRect(origin: .zero,
size: CGSize(width: 800, height: 600))
guard let consumer = CGDataConsumer(url: renderedUrl as CFURL),
let pdfContext = CGContext(consumer: consumer,
mediaBox: &mediaBox, nil)
else {
return
}
pdfContext.beginPDFPage(nil)
pdfContext.translateBy(x: mediaBox.size.width / 2 - size.width / 2,
y: mediaBox.size.height / 2 - size.height / 2)
renderer(pdfContext)
pdfContext.endPDFPage()
pdfContext.closePDF()
}
if let savedPDF = PDFDocument(url: renderedUrl) {
startPrinting(document: savedPDF)
}
}
}
}
private func createAwardView() -> some View {
VStack {
Image(systemName: "trophy")
.resizable()
.frame(width: 200, height: 200)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.shadow(color: .mint, radius: 5)
Text("name")
.font(.largeTitle)
Text("date")
}
.multilineTextAlignment(.center)
.frame(width: 200, height: 290)
}
(most of this above code is directly from the ImageRenderer documentation)
Then, you'll notice that I have a startPrinting
function.
func startPrinting(document: PDFDocument) {
let printInfo = NSPrintInfo.shared
printInfo.horizontalPagination = .fit
printInfo.verticalPagination = .fit
printInfo.orientation = .portrait
printInfo.topMargin = 0
printInfo.bottomMargin = 0
printInfo.leftMargin = 0
printInfo.rightMargin = 0
printInfo.isHorizontallyCentered = true
printInfo.isVerticallyCentered = true
let scale: PDFPrintScalingMode = .pageScaleDownToFit
let printOp = document.printOperation(for: printInfo, scalingMode: scale, autoRotate: true)
DispatchQueue.main.async {
printOp?.run()
}
}
You can customize this as you see fit, but essentially you can pass the PDF that you've created to this function and it will open a printing controller.
Important!!! Add the printing entitlement if you're using AppKit (https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_print)
And if you want to use the system command-p, add the supports print command key in the Info.plist.
Let me know if this helps,
Sydney
Are you saying I have to create a file on disk in order to print?
If you wanted to first create a PDF, yes.
Otherwise, you could do something like this, where you print the image that you receive from ImageRenderer:
Button("Save Achievement") {
let renderer = ImageRenderer(content: trophyAndDate)
if let image = renderer.nsImage {
customPrint(image: image)
}
}
func customPrint(image: NSImage) {
let printInfo = NSPrintInfo()
printInfo.topMargin = 0.0
printInfo.bottomMargin = 0.0
printInfo.leftMargin = 0.0
printInfo.rightMargin = 0.0
printInfo.orientation = .landscape
printInfo.isHorizontallyCentered = false
printInfo.isVerticallyCentered = false
printInfo.scalingFactor = 1.0
let view = NSHostingView(rootView: createAwardView())
let contentRect = NSRect(x: 0, y: 0, width: 300, height: 300)
view.frame.size = contentRect.size
let newWindow = NSWindow(
contentRect: contentRect,
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
newWindow.contentView = view
let nsImageView = NSImageView(frame: contentRect)
nsImageView.image = image
let printOperation = NSPrintOperation(view: nsImageView, printInfo: printInfo)
printOperation.printInfo.orientation = .landscape
printOperation.showsPrintPanel = true
printOperation.showsProgressPanel = true
printOperation.printPanel.options.insert(NSPrintPanel.Options.showsPaperSize)
printOperation.printPanel.options.insert(NSPrintPanel.Options.showsOrientation)
printOperation.run()
}
This one uses an NSHostingView
and inserts the image from ImageRenderer to pull up the print controller without a PDF
Doesn't printing the graphics as an image rasterize the text and graphics?