Dale Jensen
Dale Jensen

Reputation: 45

NSPrintOperation context is always nil, causing a crash. How do I set the context when printing?

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];

Upvotes: 0

Views: 520

Answers (2)

Willeke
Willeke

Reputation: 15589

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];
}

Upvotes: 0

Ron
Ron

Reputation: 748

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.

Upvotes: -1

Related Questions