iOS开发之Quartz2D生成PDF-Part2

iOS开发之Quartz2D生成PDF-Part2

在上一节当中,我们创建了一个基于Quartz2D的PDF,并在PDF中添加一线条。
在这一节,主要是添加一个logo,和绘制一个table。

下载图片资源,然后添加到工程当中。

在`PDFRenderer.m`文件中添加下面方法:
//绘制图像
+ (void)drawImage:(UIImage*)image inRect:(CGRect)rect {
    [image drawInRect:rect];
}

PDFRenderer.h中添加下面方法:

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

为了能在PDF上显示此logo,在PDFRenderer.mdrawPDF方法中添加下面代码,此代码写在UIGraphicsEndPDFContext();之前:

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

在上面的代码中,创建一个UIImage对象,并定义图像的位置和大小,调用drawImage方法将两个参数传过去进行绘制。

完整代码如下:

+ (void)drawPDF:(NSString*)fileName {
    UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
    UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);

    CGPoint from = CGPointMake(0, 0);
    CGPoint to = CGPointMake(200, 300);
    [PDFRenderer drawLineFromPoint:from toPoint:to];

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

    [self drawText];

    UIGraphicsEndPDFContext();
}

至此了解到如何绘制清单的基本元素:文本、线条、图片。接下来将运用这些所有元素构建更为完美的布局。

绘制Labels

创建一个xib,并命名为InvoiceView,选中InvoiceViewIB,设置View的width: 612 和height: 792,这些都是A4PDF的默认尺寸。下面拖拽8个UILabel,并按如下命名:

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

这些labels的位置将会在PDF上进行布局。给每个label从0-7设置tag。例如:Recipient的tag是0,Recipient’s Address的tag事1,以此类推。

打开PDFRenderer.m文件,并重构drawText方法。代码清单如下:

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

    CFStringRef stringRef = (__bridge CFStringRef)textToDraw;
    CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);

    CGMutablePathRef framePath = CGPathCreateMutable();
    CGPathAddRect(framePath, NULL, frameRect);
    CFRange currentRange = CFRangeMake(0, 0);
    CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
    CGPathRelease(framePath);

    CGContextRef currentContext = UIGraphicsGetCurrentContext();

    CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);

    CGContextTranslateCTM(currentContext, 0,100);
    CGContextScaleCTM(currentContext, 1.0, -1.0);

    CTFrameDraw(frameRef, currentContext);

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

PDFRenderer.h文件中添加下面代码:

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

下面是从InvoiceView中加载label,使用文本和位置大小来绘制到PDF上。在PDFRenderer.m中的drawPDF的商法添加一个新方法drawLabels:

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

这个方法是加载InvoiceView,遍历InvoiceView中的labels,调用drawText,将text和frame变量传递过去。

需改drawPDF方法:

+ (void)drawPDF:(NSString*)fileName {
    UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
    UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);

    [self drawText:@"Hello world" inFrame:CGRectMake(0, 0, 300, 50)];

    [self drawLabels];
//    [self drawText];

    UIGraphicsEndPDFContext();
}
运行下模拟器:

啊哈,能运行出来,但结果不是令人满意,文字啥的都是反的。接下来就坐下处理,修改drawText 的代码:

CGContextTranslateCTM(currentContext, 0, frameRect.origin.y*2);
    CGContextScaleCTM(currentContext, 1.0, -1.0);

    CTFrameDraw(frameRef, currentContext);

    CGContextScaleCTM(currentContext, 1.0, -1.0);
    CGContextTranslateCTM(currentContext, 0, (-1.0) * frameRect.origin.y * 2);

再次运行下模拟器,看下结果:


结果比较令人满意。

添加logo

打开InvoiceView.xib 添加一个UIImageView


然后在PDFRenderer.m 中添加drawLogo方法:

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

处理逻辑和drawLabels 方法类似。

最后在drawPDF 方法中的[self drawLabels] 语句后调用[self drawLogo] 。来看下运行效果:


绘制一个表格

绘制表格不能像使用InvoiceView 那样,需要一系列的变量来替代,例如:table的width和height,row的height,column的width。

下面在PDFRenderer.mdrawPDF 上方添加如下代码:

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

上面方法是绘制水平线,循环遍历每一行,计算每行的起始和结束位置。最后调用drawLine:from:to 方法绘制水平线:

+ (void)drawPDF:(NSString*)fileName {
    UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
    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];

    UIGraphicsEndPDFContext();
}

运行模拟器,看下效果:


接下来是绘制垂直线条,在drawTable 方法中的第一个循环的下方再添加一个循环:

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

再次运行下模拟器,看下效果:


看着似乎已完成,但还缺少一些数据填充到表格当中,那么接下来完成此过程,让此PDF近乎完美。

填充表格

模拟数据填充表格,在PDFRenderer.m 中的drawPDF 方法的上方添加 drawTableDataAt 方法:

+ (void)drawTableDataAt:(CGPoint)origin
          withRowHeight:(int)rowHeight
         andColumnWidth:(int)columnWidth
            andRowCount:(int)numberOfRows
         andColumnCount:(int)numberOfColumns
{
    NSArray *header = @[@"Quantity", @"Description", @"Unit price", @"Total"];
    NSArray *invoiceInfo1 = @[@"1", @"Development", @"$1000", @"1000"];
    NSArray *invoiceInfo2 = @[@"1", @"Development", @"$1000", @"1000"];
    NSArray *invoiceInfo3 = @[@"1", @"Development", @"$1000", @"1000"];
    NSArray *invoiceInfo4 = @[@"1", @"Development", @"$1000", @"1000"];

    NSArray *allInfo = @[header, invoiceInfo1, invoiceInfo2, invoiceInfo3, invoiceInfo4];

    for (int i = 0; i < allInfo.count; i++) {
        NSArray *infoToDraw = allInfo[i];
        for (int j = 0; j< numberOfColumns; j++) {
            int newOriginX = origin.x + (columnWidth * j);
            int newOriginY = origin.y + (rowHeight * (i+1));

            CGRect frame = CGRectMake(newOriginX, newOriginY, columnWidth, rowHeight);
            [self drawText:infoToDraw[j] inFrame:frame];
        }
    }
}

第一个数组是表头数据,其他数组是表中行和列的值。

drawPDF 中调用drawTableDataAt(在UIGraphicsEndPDFContext 之前调用):

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

运行模拟器,将会看到表中填充的数据:


感觉还差点什么,再做最后一次调整:添加间距padding

+ (void)drawTableDataAt:(CGPoint)origin
          withRowHeight:(int)rowHeight
         andColumnWidth:(int)columnWidth
            andRowCount:(int)numberOfRows
         andColumnCount:(int)numberOfColumns
{
    int padding = 10;
    NSArray *header = @[@"Quantity", @"Description", @"Unit price", @"Total"];
    NSArray *invoiceInfo1 = @[@"1", @"Development", @"$1000", @"1000"];
    NSArray *invoiceInfo2 = @[@"1", @"Development", @"$1000", @"1000"];
    NSArray *invoiceInfo3 = @[@"1", @"Development", @"$1000", @"1000"];
    NSArray *invoiceInfo4 = @[@"1", @"Development", @"$1000", @"1000"];

    NSArray *allInfo = @[header, invoiceInfo1, invoiceInfo2, invoiceInfo3, invoiceInfo4];

    for (int i = 0; i < allInfo.count; i++) {
        NSArray *infoToDraw = allInfo[i];
        for (int j = 0; j< numberOfColumns; j++) {
            int newOriginX = origin.x + (columnWidth * j);
            int newOriginY = origin.y + (rowHeight * (i+1));

            CGRect frame = CGRectMake(newOriginX+padding, newOriginY+padding, columnWidth, rowHeight);
            [self drawText:infoToDraw[j] inFrame:frame];
        }
    }
}

最终结果:


大功告成,此PDF主要展示了图片、表格和数据。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章