在iOS5中使用Quartz 2D创建PDF-第一部分

翻译自:How To Create a PDF with Quartz 2D in iOS 5
有时候在app中可能会需要使用app中的数据来创建PDF。例如,你有一个app允许用户签一个合同,你就可能希望用户得到最终的PDF。
但是如何使用程序来生成PDF呢?在iOS中使用Quartz2D会很容易的做到。
在这个教程中,你会亲自体验到,使用 Quartz2D 创建一个简单的 PDF。我们生成的是一个制作发票的PDF APP,如截图所示。
APP截图
这个教程需要你熟悉iOS5的新特性,例如Storyboards 和 ARC。如果你是刚接触iOS5,可以查看本站点上的iOS5教程

开始

运行Xcode,使用iOS\Application\Single View Application模板创建一个新工程。在工程名称处输入PDFRenderer,在Device Family处选择iPhone,一定要勾上Use Storyboard 和 Use Automatic Reference Counting,点击finish来创建项目。
创建工程
这个项目会有两个场景。第一个场景仅仅有一个按钮,点击的时候来展示PDF。第二个场景就是PDF本身。
选择MainStoryboard.storyboard文件。在主窗口中,你将会看到一个控制器。我们需要把这个控制器嵌入到一个导航控制器中,点击Editor菜单,然后选择Embed In/Navigation Controller。
这里写图片描述
现在视图控制器已和导航控制器联系在一起。
这里写图片描述
拖动一个UIButton到视图控制器中,命名为”Draw PDF”。
这里写图片描述
如果你运行程序,你将会看到一个名为“Draw PDF”按钮的简单视图,但当点击的时候并没有任何反应。稍后我们在说这个。
现在来添加第二个视图来显示PDF。
拖动一个新的视图控制器到Storyboard。按住 Ctrl 并拖动“Draw PDF”按钮到新的视图控制器上,你将会看到一个弹出式窗口,类似于下图所示。
这里写图片描述

选择Push选项,就会在新的视图控制器上创建已segue,当点击按钮时,就会显示新的视图控制器。换句话说,按钮现在正常工作了。
运行app,点击按钮,将会在屏幕上看到一个空的视图控制器。这就是Storyboard。

创建PDF和绘制文本

现在已经有了框架,是时候写代码了啦。
在开始之前,选择File\New\New 在工程中加入新的文件。选在 iOS\Cocoa Touch\UIViewController 子类模板,输入PDFViewController,并确保不选中“With XIB for user interface”,完成创建文件。我们不需要nib是因为使用了storyboard。
在Storyboard中选择最后添加的视图控制器,在identity inspector中把Class改为PDFViewController,这样就把它们联系起来了。
这里写图片描述
为了在PDF中绘制一些文字,我们将会使用Core Text框架。选择PDFRenderer target,在Build Phases标签栏中选择Link Binaries With Libraries,点击“+”号,选择CoreText框架。
这里写图片描述
在PDFViewController.h中,导入CoreText:

#import <CoreText/CoreText.h>

现在可以编码了!在PDFViewController.m中添加一个新方法,来创建一个“hello world”的PDF。这个方法很长,但是别担心——稍后我们将会解释它。

-(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();

}

开始的6行代码,在Documents文件夹中创建一个PDF文件。文件名为Invoice.pdf。

NSString* fileName = @"Invoice.PDF";

NSArray *arrayPaths =
NSSearchPathForDirectoriesInDomains(
                                    NSDocumentDirectory,
                                    NSUserDomainMask,
                                    YES);
NSString *path = [arrayPaths objectAtIndex:0];
NSString* pdfFileName = [path stringByAppendingPathComponent:fileName];

接下来的一段代码我们创建了一个将要绘制到PDF上的字符串“Hello world”。并将字符串转换为对应 CoreGraphics 的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);

接下来创建了一个CGRect来定义文本绘制的位置。

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

接下来创建一个PDF上下文并标记PDF页面的开始。PDF 文档的每页都从调用 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();

Core Graphics的坐标系从左下角开始绘制,而UIKit的坐标系是从左上角开始的。在开始绘制之前需要将上下文翻转。

// 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);

然后绘制文本,释放Core Graphics对象,并关闭PDF上下文。

// Draw the frame.
CTFrameDraw(frameRef, currentContext);

CFRelease(frameRef);
CFRelease(stringRef);
CFRelease(framesetter);

// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();

如果你有兴趣学习更多关于Core Text是如何工作和你可以用它做的一些更酷的事情,请查阅我们的Core Text教程

添加UIWebView来展示PDF文件

剩下要做的唯一事情是在屏幕上显示我们 PDF 文件。要做到这一点,请将下面的方法添加到 PDFViewController.m中,并将 UIWebView 添加到视图控制器中来显示我们刚刚创建的 PDF 文件路径。

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

然后在viewDidLoad中加入如下的方法:

- (void)viewDidLoad
{
    [self drawText];
    [self showPDFFile];

    [super viewDidLoad];
}

现在可以看结果了!编译并运行工程,在放大时,应该看到屏幕上的”Hello World”!
这里写图片描述

重构

我们的绘图代码并不是真的属于视图控制器,因此,我们新建一个NSObject对象叫做PDFRenderer。使用 iOS\Cocoa Touch\Objective-C 类模板创建一个NSObject的子类,叫做PDFRenderer。
在PDFRenderer.h 中导入Core Text:

#import CoreText/CoreText.h

把绘制文本的方法从PDFViewController.m移到PDFRenderer.m中。
我们把文件名放入新的文本绘制方法中,在PDFViewController.m file中创建一个新方法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;

}

然后,在PDFRenderer.m中从绘制文本方法中移除相应的代码,并修改方法把文件名作为一个参数,并使它成为一个类方法。

+(void)drawText:(NSString*)pdfFileName

并在PDFRenderer.h中声明。
然后在PDFViewController.m中导入PDFRenderer:

#import "PDFRenderer.h"

修改viewDidLoad,并调用新的类和方法:

- (void)viewDidLoad
{
    NSString* fileName = [self getPDFFileName];

    [PDFRenderer drawText:fileName];
    [self showPDFFile];

    [super viewDidLoad];
}

编译和运行工程。我们的重构并不会使应用成员表现不同,但代码组织的更好。

使用Quartz 2D画线

我们想要的最终的发票由文本、 线条和图像组成。我们有了文本 — — 现在是时候去练习画一条线了。为了做到这一点,我们将使用drawLine 方法 !

在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);

}

上面的代码设置我们想要画线的属性,线的宽度和颜色。它在传递到方法中的 CGPoints间 绘制线条。

现在可以从视图控制器中调用此方法。然而,需要注意的是通过调用 UIGraphicsBeginPDFContextToFile,drawText 方法不会创建新的 PDF 图形上下文或新的一页。所以我们需要进行一些修改。

首先,在PDFRenderer中创建一个新方法,叫做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();
    }

这个方法将会创建我们的图形上下文,绘制文本和画一条线,最后关闭上下文。

注意,drawText方法不再把PDF的文件名作为一个参数。下面是drawText方法:

+(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);
}

在 PDFRenderer.h中声明方法。在View Controller的viewDidLoad的方法中,使用drawText替代drawPDF方法。

好了!编译并运行程序,你将会看到“Hello World”和一条斜线,如下所示:

这里写图片描述

Where to Go From Here?

在这example project可以上述教程的所有代码。

这个教程结束了,在第二部分,我们将进入更高级的绘图技术,像添加图像和使用 xib 文件使布局过程更为简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值