Printing of NSTextView

I have a document-based macOS app written in Objective-C, and each document window contains a scrollable NSTextView. I know that printing can get complicated if you want to do nice pagination, but is there a quick and dirty way to get basic printing working? As it is, the print panel shows up, but its preview area is totally blank. Here's the current printing part of my NSDocument subclass.

- (NSPrintInfo *)printInfo
{
    NSPrintInfo *printInfo = [super printInfo];
	[printInfo setHorizontalPagination: NSPrintingPaginationModeFit];
	[printInfo setHorizontallyCentered: NO];
	[printInfo setVerticallyCentered: NO];
	[printInfo setLeftMargin: 72.0];
	[printInfo setRightMargin: 72.0];
	[printInfo setTopMargin: 72.0];
	[printInfo setBottomMargin: 72.0];
    return printInfo;
}


- (void)printDocumentWithSettings:(NSDictionary<NSPrintInfoAttributeKey, id> *)printSettings
	showPrintPanel:(BOOL)showPrintPanel
	delegate:(id)delegate
	didPrintSelector:(SEL)didPrintSelector
	contextInfo:(void *)contextInfo
{
	NSPrintInfo*	thePrintInfo = [self printInfo];
	
	[thePrintInfo setVerticallyCentered: NO ];
	
	NSPrintOperation *op = [NSPrintOperation
                printOperationWithView: _textView
                printInfo: thePrintInfo ];

	[op runOperationModalForWindow: _docWindow
                delegate: delegate
                didRunSelector:  didPrintSelector
                contextInfo: contextInfo];
}
Answered by DTS Engineer in 855949022

The trick here is to make an off-screen copy of your NSTextView and copy your contents into it. Here's an example of how to do that:


@IBAction func megaPrint(_ sender:Any?) {
    
    let printView = megaTextView! // NSTextView from your on-screen view hierarchy
    
    // make a new NSTextView copying the contents from your on-screen view
    // make sure to set the frame
    let printedView = NSTextView(frame: NSRect(origin:NSPoint(x:0, y:0), size:NSSize(width:480, height:200)))
    printedView.isVerticallyResizable = true
    printedView.isHorizontallyResizable = false
    printedView.textStorage!.append(printView.textStorage!.copy() as! NSAttributedString)
    
    // Create a new print operation and tell it to show the print and progress panels
    // and set some options for positioning the text in pages
    let printOperation = NSPrintOperation.init(view: printedView)
    printOperation.showsPrintPanel = true;
    printOperation.showsProgressPanel = true;
    printOperation.printInfo.isVerticallyCentered = false
    printOperation.printInfo.isHorizontallyCentered = false

    // These days, users generally expect to be able to perform page setup style changes in the print panel, aliveating the need to open the Page Setup panel seperately. Expliclty opt into that here. Note: CharacterSheet.xib will use auto layout to automatically adjust to paper changes.
    let printPanel = printOperation.printPanel
    printPanel.options = printPanel.options.union([.showsCopies, .showsPageRange, .showsPaperSize, .showsOrientation])
    
    // For fun, lets print our header and footer (note, the header is a blank string)
    let printInfo = printOperation.printInfo
    printInfo.dictionary()[NSPrintInfo.AttributeKey.headerAndFooter] = true
    
    // Run the print operation. This will show the print sheet and peform preview and printing
    // Note: As with normal sheets, code execution will not block on this line
    printOperation.runModal(for: self.view.window!, delegate: nil, didRun:nil, contextInfo: nil)
}

I tried using somewhat simpler printing code, which I've seen work in other apps:

- (IBAction) doPrint: (id) sender
{
	NSPrintOperation* printOp = [NSPrintOperation printOperationWithView: _webView];
	printOp.jobTitle = @"Help";
	NSPrintInfo* info = printOp.printInfo;
	info.verticallyCentered = NO;
	printOp.printInfo = info;
	[printOp runOperationModalForWindow: _webView.window
			delegate: self
			didRunSelector: nil
			contextInfo: nil ];
}

But no go here. I tried the same thing in another window, this one using WKWebView, with the same blank result.

This is a sandboxed app, and the com.apple.security.print entitlement is on.

I'm working in Xcode 15.2 on macOS 13.8.8. I took a project that wouldn't print on this machine to a newer machine running a Tahoe beta, and there it worked. So then I'm asking myself, is printing just broken on this machine? No, printing from other apps shows the preview just fine.

Accepted Answer

The trick here is to make an off-screen copy of your NSTextView and copy your contents into it. Here's an example of how to do that:


@IBAction func megaPrint(_ sender:Any?) {
    
    let printView = megaTextView! // NSTextView from your on-screen view hierarchy
    
    // make a new NSTextView copying the contents from your on-screen view
    // make sure to set the frame
    let printedView = NSTextView(frame: NSRect(origin:NSPoint(x:0, y:0), size:NSSize(width:480, height:200)))
    printedView.isVerticallyResizable = true
    printedView.isHorizontallyResizable = false
    printedView.textStorage!.append(printView.textStorage!.copy() as! NSAttributedString)
    
    // Create a new print operation and tell it to show the print and progress panels
    // and set some options for positioning the text in pages
    let printOperation = NSPrintOperation.init(view: printedView)
    printOperation.showsPrintPanel = true;
    printOperation.showsProgressPanel = true;
    printOperation.printInfo.isVerticallyCentered = false
    printOperation.printInfo.isHorizontallyCentered = false

    // These days, users generally expect to be able to perform page setup style changes in the print panel, aliveating the need to open the Page Setup panel seperately. Expliclty opt into that here. Note: CharacterSheet.xib will use auto layout to automatically adjust to paper changes.
    let printPanel = printOperation.printPanel
    printPanel.options = printPanel.options.union([.showsCopies, .showsPageRange, .showsPaperSize, .showsOrientation])
    
    // For fun, lets print our header and footer (note, the header is a blank string)
    let printInfo = printOperation.printInfo
    printInfo.dictionary()[NSPrintInfo.AttributeKey.headerAndFooter] = true
    
    // Run the print operation. This will show the print sheet and peform preview and printing
    // Note: As with normal sheets, code execution will not block on this line
    printOperation.runModal(for: self.view.window!, delegate: nil, didRun:nil, contextInfo: nil)
}

Printing of NSTextView
 
 
Q