13.02.2011

für CorePlot 0.2.2, Xcode 3.2

CorePlot: Drucken von Diagrammen

Im Folgenden wird erklärt, wie ein CorePlot-Diagramm auf einem Drucken ausgedruckt werden kann. Als Vorlage kann entweder Tutorial: CorePlot Liniendiagramm oder Tutorial: CorePlot Balkendiagramm verwendet werden. In dem Beispiel wird ein zusätzlicher Button in die Oberfläche eingebaut, der mit einer Methode zum Drucken verbunden wird. Die Methode öffnet den Druck-Dialog, der es dem Benutzer ermöglicht eine Druckvorschau zu öffnen, das Diagramm zu speichern und natürlich zu drucken.

Als erstes muß eine IBAction in der Header-Datei hinzugefügt werden. In dem Beispiel hat sie den Namen printDiagramm. Diese Methode soll aufgerufen werden, wenn der Benutzer den Print-Button anklickt.

-(IBAction)printDiagramm:(id)sender;

Die Implementierung sieht folgendermaßen aus:

-(IBAction)printDiagramm:(id)sender {
    NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
    [printInfo setOrientation:1];
    [printInfo setHorizontallyCentered:YES];
    [printInfo setVerticallyCentered:YES];
    [printInfo setRightMargin:0];
    [printInfo setLeftMargin:0];
    [printInfo setTopMargin:0];
    [printInfo setBottomMargin:0];

    NSData *pdfRepresentation = [view.hostedLayer dataForPDFRepresentationOfLayer];
    PDFView* pdfView = [[PDFView alloc] initWithFrame: NSMakeRect(1.0f, 1.0f, printInfo.paperSize.width, printInfo.paperSize.height)];
    PDFDocument *pdfDocument = [[PDFDocument alloc] initWithData:pdfRepresentation];
    [pdfView setDocument: pdfDocument];

    NSWindow *window = [[NSWindow alloc]init];
    [window setFrame:NSMakeRect(1.0f, 1.0f, printInfo.paperSize.width, printInfo.paperSize.height) display:false];
    [window setContentView:pdfView];

    NSPrintOperation *printOp;
    printOp = [NSPrintOperation printOperationWithView:pdfView printInfo:printInfo];
    [printOp runOperation];
}

Als erstes wird das Objekt NSPrintInfo mit Werten belegt. Danach wird eine Funktionalität von CorePlot benutzt, um über den hostedLayer der view eine PDF-Representation des Diagramms abzufragen. Der Rückgabewert ist einfach ein Objekt vom Typ NSData. Diese Daten werden verwendet, um ein PDFDocument zu erstellen, welches dann einer PDFView zugewiesen wird. Obwohl das NSWindow auf den ersten Blick unnötigt aussieht, wird es benötigt. Läßt man es weg, dann würde das Ausdrucken nicht funktionieren, weil eine NSView nicht ohne NSWindow verwendet werden sollte. Abschließend wird einfach eine NSPrintOperation erstellt und gestartet.

In der Lösung oben wurde zum Drucken eine NSPrintOperation verwendet. Allerdings besitzt die PDFView auch eine eigene print-Methode. Um diese zu nutzen sind folgende Anpassungen notwendig.

-(IBAction)printDiagramm:(id)sender {
    NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
    [printInfo setOrientation:1];
    [printInfo setHorizontallyCentered:YES];
    [printInfo setVerticallyCentered:YES];
    [printInfo setRightMargin:0];
    [printInfo setLeftMargin:0];
    [printInfo setTopMargin:0];
    [printInfo setBottomMargin:0];

    NSData *pdfRepresentation = [view.hostedLayer dataForPDFRepresentationOfLayer];
    PDFDocument *pdfDoc = [[[PDFDocument alloc] initWithData: pdfRepresentation] autorelease];

    PDFView *pdfView = [[[PDFView alloc] initWithFrame: NSMakeRect(0.0, 0.0, 0.0, 0.0)] autorelease];
    [[[NSApp keyWindow] contentView] addSubview: pdfView];
    [pdfView setDocument: pdfDoc];
    [pdfView print: nil];
}

Der Unterschied zu der Lösung oben ist, daß über die globale Konstante NSApp auf das NSWindow zugegriffen wird, welches zur Zeit Tastatur-Events empfängt. Zudem wird zur contentView eine Subview hinzugefügt. Nach dem Setzen des PDF-Dokuments pdfDoc kann dann der Print-Befehl der pdfView aufgerufen werden. Diese Lösung wurde von Chris im OS X Entwicklerforum vorgeschlagen.

Im Folgenden wir eine weitere Möglichkeit zum Drucken gezeigt. Die Lösung kommt ohne PDFView aus. Allerdings gibt es Nachteile, die auch erwähnt werden.

Was man sich natürlich fragt ist: Warum braucht man die PDFView überhaupt? Die Antwort ist: Eigentlich nur um die NSPrintOperation zu erstellen. Die nächste Frage ist: Warum kann das PDFDocument das eigentlich nicht? Die Antwort ist: Die Klasse kann es, aber die Methode ist private.

Es existiert also eine Methode - (NSPrintOperation *)getPrintOperationForPrintInfo:(NSPrintInfo *)printInfo autoRotate:(BOOL)doRotate in der Klasse PDFDocument. Diese Information habe ich von How to print a PDF with Cocoa und einen Artikel bei Stackoverflow.

Um die private Methode zu verwenden, muß einfach eine Category in der Klasse definiert werden. Für das Beispiel heißt das, daß die Category am Beginn der Datei CorePlotLiniendiagramm.m oder CorePlotBalkendiagramm.m eingefügt werden muß. Falls man die Category nicht angibt, dann wird der Compiler eine Warning anzeigen.

@interface PDFDocument (PDFPrintInfo)
- (NSPrintOperation *)getPrintOperationForPrintInfo:(NSPrintInfo *)printInfo autoRotate:(BOOL)doRotate;
@end

Nun läßt sich die Methode direkt aufrufen. Die Implementierung für die Methode printDiagramm sieht nun so aus:

-(IBAction)printDiagramm:(id)sender {
    NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
    [printInfo setOrientation:1];
    [printInfo setHorizontallyCentered:YES];
    [printInfo setVerticallyCentered:YES];
    [printInfo setRightMargin:0];
    [printInfo setLeftMargin:0];
    [printInfo setTopMargin:0];
    [printInfo setBottomMargin:0];


    NSData *pdfRepresentation = [view.hostedLayer dataForPDFRepresentationOfLayer];
    PDFDocument *pdfDocument = [[PDFDocument alloc] initWithData:pdfRepresentation];
    NSPrintOperation *printOp = [pdfDocument getPrintOperationForPrintInfo:printInfo autoRotate:YES];
    [printOp runOperation];
}

Als erstes werden wieder die Parameter für NSPrintInfo gesetzt. Danach wird die PDF-Repräsentation des CorePlot-Diagramms ausgelesen und daraus ein PDFDocument erstellt. Dieses liefert als Rückgabewert der Methode getPrintOperationForPrintInfo:autoRotate: die gewünschte NSPrintOperation. Zu beachten ist natürlich, daß es sich um eine private Methode handelt, die Apple jederzeit ändern kann und der Code dann nicht mehr funktioniert! Mit Xcode 3.2.4 klappt es aber.

Als letztes muß jetzt nur noch der Button in die Oberfläche eingefügt werden, so daß die Methode printDiagramm aufgerufen werden kann. Dazu wird der InterfaceBuilder geöffnet und es wird ein Button mit dem Titel Print eingefügt.

Der neue Button wird nun mit der Methode printDiagramm verbunden. Dazu wird im InterfaceBuilder das MainMenu.xib ausgewählt, der Push Button (Print) selektiert und mittels crtl-drag auf den AppDelegate gezogen. Dort wird printDiagramm ausgewählt.

Nach dem Speichern und Kompilieren läßt sich nun das CorePlot-Diagramm drucken. Wenn die Anwendung gestartet ist und der Button Print angeklickt wird, dann sollte ungefähr folgendes zu sehen sein:

Wird dort beispielsweise Preview ausgewählt, dann wird die Vorschau mit dem CorePlot-Diagramm angezeigt.