zoukankan      html  css  js  c++  java
  • 使用编程方式产生PDF

    此文章介绍使用Quartz2D产生一个简单的PDF文件,可以用作应用中发票的展示。此文章转自Tope Abayomi博客,源码在http://cdn4.raywenderlich.com/downloads/PDFRenderer.zip下

    此文章假设你已经熟悉ios的新功能,比如Storyboards and ARC,如果你还对ios5不熟悉,请到论坛或者博客中寻找它。

    Getting Started

    Run Xcode and create a new project with the iOS\Application\Single View Application template. Enter PDFRenderer for the project name, choose iPhone for the Device Family, make sure Use Storyboard and Use Automatic Reference Counting are checked, and finish creating the project.

    We are going to use two screens in this project. The first will simply have a button that will show the PDF when tapped. The second screen will be the PDF itself.

    Select the MainStoryboard.storyboard file. In the main window, you will see a View Controller. We need this View Controller to be embedded in a Navigation Controller to start with, so click on the Editor Menu, then select Embed In/Navigation Controller.

    The View Controller now has a segue from the Navigation Controller.

     Now drag a UIButton from the objects tab to the View Controller, and rename the label “Draw PDF.”

    If you run the application, you should see a simple View with a button displayed that says “Draw PDF,” but does nothing when tapped. We’ll take care of that shortly.

    If you run the application, you should see a simple View with a button displayed that says “Draw PDF,” but does nothing when tapped. We’ll take care of that shortly.

    Now let’s add the second View that will hold the PDF.

    Drag a new View Controller from the objects tab onto the Storyboard. Ctrl+Drag the “Draw PDF” button onto the new View Controller. When you release the mouse, you should see a popup similar to the one in the image below.

    Select the Push option. This will create a segue onto the new View Controller so that it is displayed when the button is tapped. In other words, our button is now functional!

    Run the application, tap the button, and you should see an empty View Controller pushed onto the screen. Storyboards rule!

    Creating the PDF and Drawing Text

    Now that we have the framework for our PDF, we’re ready to write some code.

    Before we do that, select File\New\New File to add a new file to the project. Choose the iOS\Cocoa Touch\UIViewController subclass template, enter PDFViewController for the Class and UIViewController for the Subclass, make sure “With XIB for user interface” is NOT checked, and finish creating the file. We do not need a nib because we will use the View Controller created in the storyboard.

    Connect the last View Controller we created to this new file by selecting the View Controller on the Storyboard and changing the class to PDFViewController in the identity inspector.

    To draw some text in our PDF, we’re going to need to use the Core Text framework. To do this, select the PDFRenderer target and go to the Build Phases tab. Click the + sign below the Link Binaries With Libraries option, and then select the CoreText framework.

    Then open PDFViewController.h and import the CoreText header:

      #import <CoreText/CoreText.h>

    OK time for the code! Add a new method to PDFViewController.m to create a “hello world” PDF. This is a long method, but don’t worry – we’ll explain it bit by bit afterwards.

    
    

    -(void)drawText {

           NSString* fileName = @"Invoice.PDF";  

           NSArray *arrayPaths =

           NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES);

           NSString *path = [arrayPaths objectAtIndex:0];

           NSString* pdfFileName = [path stringByAppendingPathComponent:fileName];  

           NSString* textToDraw = @"Hello World";

           CFStringRef stringRef = (__bridge CFStringRef)textToDraw;  

          // Prepare the text using a Core Text Framesetter.

           CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);

           CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);  

     

           CGRect frameRect = CGRectMake(0, 0, 300, 50);

           CGMutablePathRef framePath = CGPathCreateMutable();

           CGPathAddRect(framePath, NULL, frameRect);  

     

          // Get the frame that will do the rendering.

          CFRange currentRange = CFRangeMake(0, 0);

          CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);

          CGPathRelease(framePath);  

     

     

         // Create the PDF context using the default page size of 612 x 792.   

          UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectZero, nil);  

     

          // Mark the beginning of a new page.

          UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);  

     

         // Get the graphics context.

         CGContextRef currentContext = UIGraphicsGetCurrentContext();  

     

          // Put the text matrix into a known state. This ensures

          // that no old scaling factors are left in place.

          CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);  

     

          // Core Text draws from the bottom-left corner up, so flip

          // the current transform prior to drawing.

          CGContextTranslateCTM(currentContext, 0, 100);

          CGContextScaleCTM(currentContext, 1.0, -1.0);  

     

          // Draw the frame.

           CTFrameDraw(frameRef, currentContext);  

           CFRelease(frameRef);

           CFRelease(stringRef);

           CFRelease(framesetter);  

     

           // Close the PDF context and write the contents out.

           UIGraphicsEndPDFContext();  

    }

    The first six lines create a PDF filename for a file that will reside in the Documents folder. The file will be called Invoice.pdf.

    NSString* fileName = @"Invoice.PDF";  

    NSArray *arrayPaths =

    NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES);

    NSString *path = [arrayPaths objectAtIndex:0];

    NSString* pdfFileName = [path stringByAppendingPathComponent:fileName];

    The next block of code creates a “Hello world” string that we will draw onto the PDF. It also converts the string to its CoreGraphics counterpart, CFStringRef.

    NSString* textToDraw = @"Hello World";

    CFStringRef stringRef = (__bridge CFStringRef)textToDraw;  

     

    // Prepare the text using a Core Text Framesetter.

    CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);

    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);


    Now we create a CGRect that defines the frame where the text will be drawn.

    CGRect frameRect = CGRectMake(0, 0, 300, 50);

    CGMutablePathRef framePath = CGPathCreateMutable();

    CGPathAddRect(framePath, NULL, frameRect);  

     

    // Get the frame that will do the rendering.

    CFRange currentRange = CFRangeMake(0, 0);

    CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL); CGPathRelease(framePath);

     

    Next we create a PDF context and mark the beginning of a PDF page. Each page of the PDF has to start with a call to UIGraphicsBeginPDFPageWithInfo.

     

    // Create the PDF context using the default page size of 612 x 792. UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectZero, nil);  

     

    // Mark the beginning of a new page.

    UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);  

     

    // Get the graphics context.

    CGContextRef currentContext = UIGraphicsGetCurrentContext();

     

    The coordinates of Core Graphics drawings start from the bottom-left corner, while UIKit global coordinates start from the top-left. We need to flip the context before we begin drawing.

    // Put the text matrix into a known state. This ensures

    // that no old scaling factors are left in place.

    CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);

    // Core Text draws from the bottom-left corner up, so flip

    // the current transform prior to drawing.

    CGContextTranslateCTM(currentContext, 0, 100);

    CGContextScaleCTM(currentContext, 1.0, -1.0);

    Then we draw the actual frame with the text, release all the Core Graphics objects, and close the PDF context (hence writing the PDF to disk).
     

    // Draw the frame.

    CTFrameDraw(frameRef, currentContext);  

    CFRelease(frameRef);

    CFRelease(stringRef);

    CFRelease(framesetter);

    // Close the PDF context and write the contents out.

    UIGraphicsEndPDFContext();


    If you are interested in learning more about how Core Text works and some more cool things you can do with it, check out our Core Text tutorial.(使用Core Text做的杂志类教程)。

     

    Add a UIWebView to Show the PDF File

    The only thing left to do is to show our PDF file on the screen. To do that, add the following method to PDFViewController.m, which adds a UIWebView to the View Controller and shows the PDF file path we just created.

    -(void)showPDFFile {

          NSString* fileName = @"Invoice.PDF";

          NSArray *arrayPaths =

          NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES);

          NSString *path = [arrayPaths objectAtIndex:0];

          NSString* pdfFileName = [path stringByAppendingPathComponent:fileName];  

          UIWebView* webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];

          NSURL *url = [NSURL fileURLWithPath:pdfFileName];

          NSURLRequest *request = [NSURLRequest requestWithURL:url];

          [webView setScalesPageToFit:YES];

          [webView loadRequest:request];  

          [self.view addSubview:webView];

    }

     

    Then add the implementation of viewDidLoad to call these new methods:

     

    - (void)viewDidLoad {

        [self drawText];

        [self showPDFFile];  

        [super viewDidLoad];

    }

     

    Now we’re ready to see some results! Build and run the project, and you should see “Hello World” on the screen when you zoom in!

     

    A Quick Refactoring Process(一个快速重构过程)

    Our drawing code does not really belong in the View Controller, so let’s farm that off into a new NSObject called PDFRenderer. Create a new file with the iOS\Cocoa Touch\Objective-C class template, enter PDFRenderer for the Class and NSObject for the subclass, and finish creating the file.

    Open up PDFRenderer.h and import Core Text at the top of the file:

    #import <CoreText/CoreText.h>

    Then move the drawText method from PDFViewController.m to PDFRenderer.m.

    We will pass the filename into the new drawText method, so let’s create a new method in the PDFViewController.m file called getPDFFileName.

    -(NSString*)getPDFFileName {

        NSString* fileName = @"Invoice.PDF";  

        NSArray *arrayPaths =

        NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES);

        NSString *path = [arrayPaths objectAtIndex:0];

        NSString* pdfFileName = [path stringByAppendingPathComponent:fileName];  

        return pdfFileName;  

    }

     

    Next, open PDFRenderer.m and remove this same block of code from the drawText method(PDFViewController), and modify the method signature to take the filename as a parameter and make it a static method:

     +(void)drawPDF:(NSString*)fileName;

    Also predeclare this method in PDFRenderer.h.

    +(void)drawPDF:(NSString*)fileName
    {
        // Create the PDF context using the default page size of 612 x 792.
        UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
        // Mark the beginning of a new page.
        UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
           
        CGPoint from = CGPointMake(0, 0);
        CGPoint to = CGPointMake(200, 300);
        [PDFRenderer drawLineFromPoint:from toPoint:to];
        
        [self drawText];
        
        // Close the PDF context and write the contents out.
        UIGraphicsEndPDFContext();
    }}

    Next, import PDFRenderer at the top of PDFViewController.m:

     #import "PDFRenderer.h"

    And modify viewDidLoad to call this new class and method:

    - (void)viewDidLoad {

       NSString* fileName = [self getPDFFileName];  

       [PDFRenderer drawPDF:fileName];

       [self showPDFFile];  

       [super viewDidLoad];

    }

     

    Build and run the project. Our quick refactoring shouldn’t have caused the application to behave any differently, but the code is better organized.

     

    Drawing a Line Using Quartz 2D

    The invoice we want to end up with is made up of text, lines and images. We’ve got text — now it’s time to practice drawing a line. To do that, we will use… wait for it… the drawLine method!

    Add this new method to PDFRenderer.m:

    +(void)drawLineFromPoint:(CGPoint)from toPoint:(CGPoint)to {

        CGContextRef context = UIGraphicsGetCurrentContext();  

        CGContextSetLineWidth(context, 2.0);  

        CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();  

        CGFloat components[] = {0.2, 0.2, 0.2, 0.3};  

        CGColorRef color = CGColorCreate(colorspace, components);    

        CGContextSetStrokeColorWithColor(context, color);    

        CGContextMoveToPoint(context, from.x, from.y);

        CGContextAddLineToPoint(context, to.x, to.y);  

        CGContextStrokePath(context);

        CGColorSpaceRelease(colorspace);

        CGColorRelease(color);  

    }

     

     

    The above code sets the properties of the line we want to draw. The properties are the thickness of the line (2.0) and the color (transparent gray). It then draws the line between the CGPoints passed into the method.

    We could now call this method from our View Controller. Notice, however, that the drawText method does not create a new PDF Graphics context or a new page by calling UIGraphicsBeginPDFContextToFile. So we need to make some modifications.

    First, create a new method in the PDFRenderer file called drawPDF.

    +(void)drawPDF:(NSString*)fileName {

       // Create the PDF context using the default page size of 612 x 792.     

       UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);

       // Mark the beginning of a new page.

       UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);  

       CGPoint from = CGPointMake(0, 0); CGPoint to = CGPointMake(200, 300);

       [PDFRenderer drawLineFromPoint:from toPoint:to];  

       [self drawText];  

       // Close the PDF context and write the contents out.

       UIGraphicsEndPDFContext();

    }

     

    This will create our graphics context, draw the text and a test line, and then end the context.

    Note that the drawText method no longer takes the PDF filename as a parameter. Here is our new drawText method.

    +(void)drawText {  

        NSString* textToDraw = @"Hello World";

        CFStringRef stringRef = (__bridge CFStringRef)textToDraw;

        // Prepare the text using a Core Text Framesetter

         CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);

        CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);

        CGRect frameRect = CGRectMake(0, 0, 300, 50);

        CGMutablePathRef framePath = CGPathCreateMutable();

        CGPathAddRect(framePath, NULL, frameRect);  

        // Get the frame that will do the rendering.

        CFRange currentRange = CFRangeMake(0, 0);

        CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);  

        CGPathRelease(framePath);  

        // Get the graphics context.

        CGContextRef currentContext = UIGraphicsGetCurrentContext();

        // Put the text matrix into a known state. This ensures

        // that no old scaling factors are left in place.

        CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);  

        // Core Text draws from the bottom-left corner up, so flip

        // the current transform prior to drawing.

         CGContextTranslateCTM(currentContext, 0, 100);

         CGContextScaleCTM(currentContext, 1.0, -1.0);  

         // Draw the frame.

         CTFrameDraw(frameRef, currentContext);  

         CFRelease(frameRef);

         CFRelease(stringRef);

         CFRelease(framesetter);

    }

    Add the predeclaration for drawPDF into PDFRenderer.h. Then modify the View Controller to call the correct method in viewDidLoad, which is drawPDF instead of drawText.

    That’s it! Build and run the app, you should see both “Hello World” and a diagonal line, similar to the image below.

    2.

    This is a blog post by iOS Tutorial Team member Tope Abayomi, an iOS developer and Founder of App Design Vault, your source for iPhone App Design.

    代码地址http://cdn4.raywenderlich.com/downloads/iOSPDFRenderer2.zip

     

     

    Learn how to create a PDF programmatically!

    Learn how to create a PDF programmatically!

    Welcome to the second part of the tutorial series on how to generate a PDF using Quartz 2D!

    In Part One, we set up an app framework for the PDF, and we drew some rudimentary text and lines with Quartz 2D inside our PDF.

    Since our PDF will be an invoice document, it needs to look professional. To accomplish this, here in Part Two we’ll be adding a logo image and drawing a table to hold the invoice data. By the end of this article, our PDF will be complete.

    Let’s jump back in!

    Adding An Image

    The image we’re going to draw to our PDF is the logo of none other than RayWenderlich.com. All part of Ray’s scheme to take over the world! ;]

    Go ahead and download the image, then add it to the project. To do this, control-click on the PDFRenderer group in the Project navigator and choose “Add Files To iOSPDFRenderer.” Select the ‘ray-logo.png’ image you downloaded and then click Add.

    Next open PDFRenderer.m and add this method:

     +(void)drawImage:(UIImage*)image inRect:(CGRect)rect
     {
     
        [image drawInRect:rect];
     
     }

    Yes, drawing an image with Core Graphics is that easy! All we need is this one-liner to draw an image to the current context.

    The method takes in the image we want to draw and the frame where it will be drawn, and draws the image into the context.

    Next add the definition of this method in PDFRenderer.h:

     +(void)drawImage:(UIImage*)image inRect:(CGRect)rect;

    Now we need to call the method so that it’s displayed on the PDF. Add the following lines of code to the drawPDF method on the PDFRenderer.m file (right before the call to UIGraphicsEndPDFContext):

     UIImage* logo = [UIImage imageNamed:@"ray-logo.png"];
     CGRect frame = CGRectMake(20, 100, 300, 60);
     [PDFRenderer drawImage:logo inRect:frame];

    In the code above, we create a UIImage from the image file, define the location and size of the image to be drawn, then call the drawImage method we created above with these two parameters.

    Here’s the complete version of the drawPDF method:

     +(void)drawPDF:(NSString*)fileName
     {
        // Create the PDF context using the default page size of 612 x 792.
        UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
        // Mark the beginning of a new page.
        UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
     
        [self drawText];
     
        CGPoint from = CGPointMake(0, 0);
        CGPoint to = CGPointMake(200, 300);
        [PDFRenderer drawLineFromPoint:from toPoint:to];
     
        UIImage* logo = [UIImage imageNamed:@"ray-logo.png"];
        CGRect frame = CGRectMake(20, 100, 300, 60);
     
        [PDFRenderer drawImage:logo inRect:frame];
     
        // Close the PDF context and write the contents out.
        UIGraphicsEndPDFContext();
     }

    Now if you run the application in the simulator, you’ll see our PDF as we left it in Part One, only now with Ray’s logo. Of course, it still looks pretty crappy. Time to fix that!

    Drawing the Labels

    At this point, we know how to draw the basic elements of the invoice: text, lines and images. The next step is to combine all the elements to create a polished layout.

    To do this, we’ll use a little trick. We’ll create a View in the interface builder and add the elements for the invoice. Then we’ll use the locations of the views to make the layout.

    This will make things a lot easier because we can visually layout the PDF, and won’t have to hard-code as many coordinates for drawing. Don’t worry, it will begin to make sense as we do it!

    Create a new View in the application by choosing File\New\New File. Select the iOS\User Interface\View template and click on Next. Make sure the Device family is set to iPhone and click Next.

    Give the new View the name InvoiceView and click Create. Select the View on the Interface Builder and delete it by hitting the backspace button.

    Add a new View from the Objects tab onto the canvas. Resize the view to be 612 wide and 792 tall. These are the default dimensions for an A4 PDF file.

    Add eight labels to the View and give them the following names:

    • Recipient [Name]
    • Recipient’s Address
    • Recipient’s City
    • Recipient’s Postal Code
    • Invoicer [Name]
    • Invoicer’s Address
    • Invoicer’s City
    • Invoicer’s Postal Code

    The positions of these labels will form our layout for the invoice. Give each label a tag from 0-7. For example, the label “Recipient” will have a tag of 0, “Recipient’s Address” will have a tag of 1, and so on.

    We’re done with the layout view for now. We’ll come back to it in a bit.

    But first we need to open PDFRenderer.m and refactor the drawText method. We want to pass in the text to be drawn and the frame that will encompass it, rather than hard-coding it in.

    Go ahead and replace drawText with the following (pretty much the same except pulling out the hardcoded string and frame):

     +(void)drawText:(NSString*)textToDraw inFrame:(CGRect)frameRect
     {
        CFStringRef stringRef = (__bridge CFStringRef)textToDraw;
        // Prepare the text using a Core Text Framesetter.
        CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);
        CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
     
        CGMutablePathRef framePath = CGPathCreateMutable();
        CGPathAddRect(framePath, NULL, frameRect);
     
        // Get the frame that will do the rendering.
        CFRange currentRange = CFRangeMake(0, 0);
        CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
        CGPathRelease(framePath);
     
        // Get the graphics context.
        CGContextRef    currentContext = UIGraphicsGetCurrentContext();
     
        // Put the text matrix into a known state. This ensures
        // that no old scaling factors are left in place.
        CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
     
        // Core Text draws from the bottom-left corner up, so flip
        // the current transform prior to drawing.
        CGContextTranslateCTM(currentContext, 0, 100);
        CGContextScaleCTM(currentContext, 1.0, -1.0);
     
        // Draw the frame.
        CTFrameDraw(frameRef, currentContext);
     
        CFRelease(frameRef);
        CFRelease(stringRef);
        CFRelease(framesetter);
     }

    Then add the new definition to PDFRenderer.h:

     +(void)drawText:(NSString*)textToDraw inFrame:(CGRect)frameRect;

    The next step is to load the labels from the InvoiceView and use the text and the frame to draw to the PDF. Add this new method to PDFRenderer.m, right above drawPDF:

     +(void)drawLabels
     {    
       NSArray* objects = [[NSBundle mainBundle] loadNibNamed:@"InvoiceView" owner:nil options:nil];
     
        UIView* mainView = [objects objectAtIndex:0];
     
        for (UIView* view in [mainView subviews]) {
            if([view isKindOfClass:[UILabel class]])
            {
                UILabel* label = (UILabel*)view;
     
                [self drawText:label.text inFrame:label.frame];
            }
        }
     }

    This will load the labels from the InvoiceView, loop through all the labels and call the drawText method with its text and frame variables.

    Next we’ll modify the drawPDF method call this method and remove all the test drawing code we used previously. Replace drawPDF with the following:

     +(void)drawPDF:(NSString*)fileName
     {
        // Create the PDF context using the default page size of 612 x 792.
        UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
        // Mark the beginning of a new page.
        UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
     
        [self drawText:@"Hello World" inFrame:CGRectMake(0, 0, 300, 50)];
     
        [self drawLabels];
     
        // Close the PDF context and write out the contents.
        UIGraphicsEndPDFContext();
     }

    Let’s try it out! Run the application in the simulator, and you will see something similar to the image below.

    Yikes – it looks like garbage, right? That’s because the text is being flipped and translated multiple times in the the drawText method. Up until now, we’ve only drawn one line of text, but the problem arises when we want to draw multiple lines of text.

    To fix this, we need to modify the drawText method to flip the current context back to its original coordinates. Change the following lines in the drawText method to add the reverse-flip actions and take the origin into consideration:

        // Core Text draws from the bottom-left corner up, so flip
        // the current transform prior to drawing.
        // Modify this to take into consideration the origin.
        CGContextTranslateCTM(currentContext, 0, frameRect.origin.y*2);
        CGContextScaleCTM(currentContext, 1.0, -1.0);
     
        // Draw the frame.
        CTFrameDraw(frameRef, currentContext);
     
     
        // Add these two lines to reverse the earlier transformation.
        CGContextScaleCTM(currentContext, 1.0, -1.0);
        CGContextTranslateCTM(currentContext, 0, (-1)*frameRect.origin.y*2);

    Now run the application again. You should see a nicely laid-out PDF with all our labels mapped.

    Adding the Logo

    Next, open InvoiceView.xib and add a UIImageView to the upper right for the logo:

    Then add this new method into PDFRenderer.m (right before drawPDF):

     +(void)drawLogo
     {    
        NSArray* objects = [[NSBundle mainBundle] loadNibNamed:@"InvoiceView" owner:nil options:nil];
     
        UIView* mainView = [objects objectAtIndex:0];
     
        for (UIView* view in [mainView subviews]) {
            if([view isKindOfClass:[UIImageView class]])
            {            
                UIImage* logo = [UIImage imageNamed:@"ray-logo.png"];
                [self drawImage:logo inRect:view.frame];
            }
        }    
     }

    This loads the UIImageView from the Xib file in the same way we load the labels. Then it draws the logo onto the PDF using the coordinates of the UIImageView.

    Finally, call this method after drawLabels in the drawPDF method:

     +(void)drawPDF:(NSString*)fileName
     {
        // Create the PDF context using the default page size of 612 x 792.
        UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
        // Mark the beginning of a new page.
        UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
     
        [self drawText:@"Hello World" inFrame:CGRectMake(0, 0, 300, 50)];
     
        [self drawLabels];
        [self drawLogo];
     
        // Close the PDF context and write out the contents.
        UIGraphicsEndPDFContext();
     }

    Run the application in the simulator, and you’ll see the logo in the top right corner just as we specified!

    Pretty cool how easy it is to visually lay out the PDF with the aid of UIView coordinates, eh?

    Drawing the Table

    It’s time to add the table that will hold all the invoice information. Our table will be nothing more than an arrangement of vertical and horizontal lines.

    In this case, we won’t use the InvoiceView. Instead we’ll use a series of variables like the table height and width, and the row height and column width.

    Add the following method to PDFRenderer.m (right before drawPDF):

     +(void)drawTableAt:(CGPoint)origin 
        withRowHeight:(int)rowHeight 
       andColumnWidth:(int)columnWidth 
          andRowCount:(int)numberOfRows 
       andColumnCount:(int)numberOfColumns
     
     {   
        for (int i = 0; i <= numberOfRows; i++) 
        {        
            int newOrigin = origin.y + (rowHeight*i);
     
            CGPoint from = CGPointMake(origin.x, newOrigin);
            CGPoint to = CGPointMake(origin.x + (numberOfColumns*columnWidth), newOrigin);
     
            [self drawLineFromPoint:from toPoint:to];
        }
     }

    This method draws our horizontal lines. At the beginning of the method, we pass in the values for the starting position of the table, the number of rows and columns, the height of each row and the width of each column.

    The loop then goes through the motions for each row, calculating where the row should start and where it should end. Finally, the drawLine:from:to method is called to draw the line. We then add the following code to the drawPDF method:

     +(void)drawPDF:(NSString*)fileName
     {
        // Create the PDF context using the default page size of 612 x 792.
        UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
        // Mark the beginning of a new page.
        UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
     
        [self drawText:@"Hello World" inFrame:CGRectMake(0, 0, 300, 50)];
     
        [self drawLabels];
        [self drawLogo];
     
        int xOrigin = 50;
        int yOrigin = 300;
     
        int rowHeight = 50;
        int columnWidth = 120;
     
        int numberOfRows = 7;
        int numberOfColumns = 4;
     
        [self drawTableAt:CGPointMake(xOrigin, yOrigin) withRowHeight:rowHeight andColumnWidth:columnWidth andRowCount:numberOfRows andColumnCount:numberOfColumns];
     
        // Close the PDF context and write the contents out.
        UIGraphicsEndPDFContext();
     }

    Run the application in the simulator and you should see the horizontal lines on the PDF.

    The next step is to draw the vertical lines. Add another loop to the drawTable method below the first loop:

     +(void)drawTableAt:(CGPoint)origin 
        withRowHeight:(int)rowHeight 
       andColumnWidth:(int)columnWidth 
          andRowCount:(int)numberOfRows 
       andColumnCount:(int)numberOfColumns
     
     {   
        for (int i = 0; i <= numberOfRows; i++) 
        {        
            int newOrigin = origin.y + (rowHeight*i);
     
            CGPoint from = CGPointMake(origin.x, newOrigin);
            CGPoint to = CGPointMake(origin.x + (numberOfColumns*columnWidth), newOrigin);
     
            [self drawLineFromPoint:from toPoint:to];        
        }
     
        for (int i = 0; i <= numberOfColumns; i++) 
        {        
            int newOrigin = origin.x + (columnWidth*i);
     
            CGPoint from = CGPointMake(newOrigin, origin.y);
            CGPoint to = CGPointMake(newOrigin, origin.y +(numberOfRows*rowHeight));
     
            [self drawLineFromPoint:from toPoint:to];        
         }
     }

    The second loop in the above code does a similar run through all the columns in the table, calculating the start and end points of each line and drawing the line to the PDF.

    If you run the application again, you will see that our table is complete!

    But what use is a table without data?

    Populating the Table

    We’re going to manually add some dummy data to our table using a series of arrays. But you could easily modify this to feed in data inputed by the user.

    Add the following method called drawTableDataAt to PDFRenderer.m (right above drawPDF):

    +(void)drawTableDataAt:(CGPoint)origin 
        withRowHeight:(int)rowHeight 
       andColumnWidth:(int)columnWidth 
          andRowCount:(int)numberOfRows 
       andColumnCount:(int)numberOfColumns
     {  
        NSArray* headers = [NSArray arrayWithObjects:@"Quantity", @"Description", @"Unit price", @"Total", nil];
        NSArray* invoiceInfo1 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
        NSArray* invoiceInfo2 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
        NSArray* invoiceInfo3 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
        NSArray* invoiceInfo4 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
     
        NSArray* allInfo = [NSArray arrayWithObjects:headers, invoiceInfo1, invoiceInfo2, invoiceInfo3, invoiceInfo4, nil];
     
        for(int i = 0; i < [allInfo count]; i++)
        {
            NSArray* infoToDraw = [allInfo objectAtIndex:i];
     
            for (int j = 0; j < numberOfColumns; j++) 
            {
     
                int newOriginX = origin.x + (j*columnWidth);
                int newOriginY = origin.y + ((i+1)*rowHeight);
     
                CGRect frame = CGRectMake(newOriginX, newOriginY, columnWidth, rowHeight);
     
                [self drawText:[infoToDraw objectAtIndex:j] inFrame:frame];
            }        
        }    
     }

    This code block begins by creating the data that will be in the table. The first array contains the values that will be in the header (first row of the table). The next three arrays contain the values for each row and column of the table.

    The final array is a master array containing all the others, effectively making it a two-dimensional array modeling our table data.

    After that, there are two nested loops. The outer loop runs through each row and extracts the data for the row.

    The inner loop runs through each column and calculates the starting point of the text, depending on its location in the table. It creates a frame for the text and then draws the text in the frame.

    Add a call to this new method in the drawPDF method (right before the call to UIGraphicsEndPDFContext):

    [self drawTableDataAt:CGPointMake(xOrigin, yOrigin) withRowHeight:rowHeight andColumnWidth:columnWidth andRowCount:numberOfRows andColumnCount:numberOfColumns];

    Run the application in the simulator and you will see our table is populated with data.

    Looks good, doesn’t it? But let’s make one final tweak: we could use some padding between the table lines and the data itself.

    Below is the final state of the drawTableAt method:

    +(void)drawTableDataAt:(CGPoint)origin 
        withRowHeight:(int)rowHeight 
       andColumnWidth:(int)columnWidth 
          andRowCount:(int)numberOfRows 
       andColumnCount:(int)numberOfColumns
    {
        int padding = 10; 
     
        NSArray* headers = [NSArray arrayWithObjects:@"Quantity", @"Description", @"Unit price", @"Total", nil];
        NSArray* invoiceInfo1 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
        NSArray* invoiceInfo2 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
        NSArray* invoiceInfo3 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
        NSArray* invoiceInfo4 = [NSArray arrayWithObjects:@"1", @"Development", @"$1000", @"$1000", nil];
     
        NSArray* allInfo = [NSArray arrayWithObjects:headers, invoiceInfo1, invoiceInfo2, invoiceInfo3, invoiceInfo4, nil];
     
        for(int i = 0; i < [allInfo count]; i++)
        {
            NSArray* infoToDraw = [allInfo objectAtIndex:i];
     
            for (int j = 0; j < numberOfColumns; j++) 
            {            
                int newOriginX = origin.x + (j*columnWidth);
                int newOriginY = origin.y + ((i+1)*rowHeight);
     
                CGRect frame = CGRectMake(newOriginX + padding, newOriginY + padding, columnWidth, rowHeight);
     
                [self drawText:[infoToDraw objectAtIndex:j] inFrame:frame];
            }        
        }    
    }

    Now we can sit back and admire the final invoice PDF, displaying image, table and data. Our work is done… that is, until it’s time for all of us to start billing for RayWenderlich.com. :P

    Where to Go From Here?

    Here is an example project with all of the code from the above tutorial.

    That’s all folks! This tutorial series should have given you an idea of how to use Quartz 2D to generate a PDF displaying results from your app. From here, there are many implementation possibilities!

    As I said at the beginning of this tutorial series, going through this project should have also helped you appreciate all of the low-level layout Apple gives us for free in the UIKit.

    I look forward to reading your questions and comments in the forum discussion below!


    This is a blog post by iOS Tutorial Team member Tope Abayomi, an iOS developer with a passion for easy to use, useful, aesthetically pleasing apps. Here are some of his videos teaching you how to design an app with custom design.

     

     

  • 相关阅读:
    吴裕雄--天生自然 R语言开发学习:广义线性模型(续一)
    吴裕雄--天生自然 R语言开发学习:广义线性模型
    吴裕雄--天生自然 R语言开发学习:重抽样与自助法(续一)
    吴裕雄--天生自然 R语言开发学习:重抽样与自助法
    吴裕雄--天生自然 R语言开发学习:中级绘图(续二)
    吴裕雄--天生自然 R语言开发学习:中级绘图(续一)
    吴裕雄--天生自然 R语言开发学习:中级绘图
    吴裕雄--天生自然 R语言开发学习:功效分析(续一)
    吴裕雄--天生自然 R语言开发学习:功效分析
    吴裕雄--天生自然 R语言开发学习:方差分析(续二)
  • 原文地址:https://www.cnblogs.com/jiangshiyong/p/2669825.html
Copyright © 2011-2022 走看看