0

I have a custom NSView that I want to print. After setting things up with the NSView and the print options, I make this call:

NSPrintOperation *printOperation = [NSPrintOperation printOperationWithView: printView printInfo: printInfo];

[NSPrintOperation setCurrentOperation: printOperation];
[printView beginDocument];

NSGraphicsContext* theContext = printOperation.context;

"theContext" is always nil. If I ignore that, when I make this call:

[printView beginPageInRect: rect atPlacement: location];

I get an exception, saying: "[General] lockFocus/unlockFocus sent to a view which is not in a window"

If I comment that out, I get about a billion messages that say this: "CGContextDrawPath: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable." Turning on the backtrace just shows all of my drawing is what is causing it.

If I look at the graphics context within my view's "DrawRect:" function:

NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext];
CGContextRef      context = [[NSGraphicsContext currentContext] graphicsPort];

both graphicsContext and context are nil.

So, what do I have to do to get a valid printing context? I see that there is a NSPrintOperation method createContext, but the docs say not to call it directly, and if I ignore that, it doesn't help and shoots about eight empty jobs to the printer.

Latest version of code, which still results in a null context:

NSPrintOperation *printOperation = [NSPrintOperation printOperationWithView: printView printInfo: printInfo];

[printView setCurrentForm: [formSet objectAtIndex: 0]];

NSInteger pageCounter = 0;
formHeight = 0;
formWidth = 0;

for (AFVForm *oneForm in formSet)
{
    printView.verticalOffset = formHeight;
    NSRect  rect = NSMakeRect(0, 0, oneForm.pageWidth, oneForm.pageHeight);
    NSPoint location = [printView locationOfPrintRect: rect];

    formHeight += [oneForm pageHeight];
    if ([oneForm pageWidth] > formWidth)
        formWidth = [oneForm pageWidth];
    pageCounter++;
    printView.currentForm = oneForm;
    [printView setPrintMode: YES];

    [printView drawRect: NSZeroRect];

    [printView setPrintMode: NO];
}

[printOperation setShowsPrintPanel:YES];
[printOperation runOperationModalForWindow: [self window] delegate: nil didRunSelector: nil contextInfo: nil];
  • Figured it out -- the views get drawn during "runOperationModalForWindow:" with the context properly set, meaning that the whole "for (AFVForm *oneForm in formSet)" section was unnecessary and was what was causing all the errors. Taking that out resolved the issue. – Dale Jensen Mar 18 '19 at 17:54

2 Answers2

0

beginDocument and beginPageInRect:atPlacement: are called at the beginning of the printing session and at the beginning of each page. Override these methods if you want but don't call them. Don't call setCurrentOperation, just create a NSPrintOperation and call runOperation or runOperationModalForWindow:delegate:didRunSelector:contextInfo:.

See Printing Programming Guide for Mac

Edit, example:

PrintView.h

@interface PrintView : NSView

@property (strong) NSArray *forms;

@end

PrintView.m

@interface PrintView ()

@property (weak) NSTextField *titleField;
@property (weak) NSDictionary *currentForm;

@end


@implementation PrintView

- (instancetype)initWithFrame:(NSRect)frameRect {
    if (self = [super initWithFrame:frameRect]) {
        // add a title text field
        NSTextField *textField = [[NSTextField alloc] initWithFrame:NSMakeRect(25.0, 225.0, 250.0, 25.0)];
        textField.alignment = NSTextAlignmentCenter;
        [self addSubview:textField];
        self.titleField = textField;
    }
    return self;
}

- (BOOL)knowsPageRange:(NSRangePointer)range {
    range->location = 1;
    range->length = self.forms.count;
    return YES;
}

- (NSRect)rectForPage:(NSInteger)page {
    return self.bounds;
}

- (void)beginPageInRect:(NSRect)rect atPlacement:(NSPoint)location {
    [super beginPageInRect:rect atPlacement:location];
    NSPrintOperation *printOperation = [NSPrintOperation currentOperation];
    self.currentForm = self.forms[printOperation.currentPage - 1];
    // set the title
    [self.titleField setStringValue:
        [NSString stringWithFormat:@"Form ‘%@’ page %li",
            self.currentForm[@"title"], (long)printOperation.currentPage]];
}

- (void)drawRect:(NSRect)dirtyRect {
    [super drawRect:dirtyRect];
    // Drawing code here.
    // draw a colored frame
    NSColor *formColor = self.currentForm[@"color"];
    NSRect rect = self.bounds;
    NSInsetRect(rect, 20.0, 20.0);
    [formColor set];
    NSFrameRect(rect);
}

@end

somewhere else

- (IBAction)printAction:(id)sender {
    PrintView *printView = [[PrintView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 300.0, 300.0)];
    printView.forms = @[
            @{@"title":@"Form A", @"color":[NSColor redColor]},
            @{@"title":@"Form B", @"color":[NSColor greenColor]},
            @{@"title":@"Form C", @"color":[NSColor blueColor]},
        ];
    NSPrintOperation *printOperation = [NSPrintOperation printOperationWithView:printView];
    [printOperation setShowsPrintPanel:YES];
    [printOperation runOperationModalForWindow:[self window] delegate:nil didRunSelector:NULL contextInfo:NULL];
}
Willeke
  • 14,578
  • 4
  • 19
  • 47
  • I've read the Programming Guide over and over and can't find anything that explains why the context is nil. I've changed the code to reflect your suggestions, same thing -- the context is nil, and I get a bunch of "CGContextDrawPath: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable." because of the null context. – Dale Jensen Mar 16 '19 at 01:24
  • @DaleJensen Did you remove `[printView beginDocument];` and `[printView beginPageInRect: rect atPlacement: location];`? Please post a [mcve](https://stackoverflow.com/help/mcve) in the question. – Willeke Mar 16 '19 at 01:46
-1

A Swift example, goes in your NSView subclass. I apologize if it isn't helpful:

         func letsPrint() {
             let pInfo = NSPrintInfo.shared
             let d = pInfo.dictionary()
             d["NSLastPage"] = 1
             super.printView(self)
         }
         override func knowsPageRange(_ range: NSRangePointer) -> Bool {
             return true
         }
         override func rectForPage(_ page: Int) -> NSRect {
             if page > 1 { return NSZeroRect }
             return NSRect(x: 0, y: 0, width: 612, height: 792)
         }

This example prints 1 page, 8.5 x 11, hence the constants in rectForPage

letsPrint is wired to the app menu first responder.

Ron
  • 660
  • 1
  • 5
  • 10
  • Not a great example. `knowsPageRange` returns `true`, what is `range`? Better use `NSPrintInfo.AttributeKey.lastPage` instead of `"NSLastPage"`. Why do you set `d["NSLastPage"]`? `super.printView` instead of `self.printView`? The preview shows an active > button. – Willeke Mar 16 '19 at 01:36