美文网首页iOS 技巧
iOS 绘制PDF页面并隔空打印出来

iOS 绘制PDF页面并隔空打印出来

作者: cocoaCoffee | 来源:发表于2021-01-29 17:12 被阅读0次

    需求: 移动端绘制PDF页面, 获取数据进行填充, 然后通过隔空打印打印出来,以下内容只根据我所做具体内容进行讲解,如有不对,还请不吝指教

    一、创建PDF文件

    创建PDF文件有两种方式, 一种是用UIKit的UIGraphics类里面的方法,第二种是用CoreGraphics, 是基于Quartz 2D的一个高级绘图引擎,。Core Graphics是对底层C语言的一个简单封装,而UIGraphics是对CoreGraphics的部分功能的进一层封装, 所以UIGraphics更加苹果点, 使用方便, 而CoreGraphics更底层, 更强大

    UIGraphics创建PDF

    1. 用UIGraphicsBeginPDFContextToFile方法创建

    // 指定PDF文件保存路径
    NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *pathPDF =  [documentPath stringByAppendingPathComponent:@"绘制.pdf"];
    // 创建以指定路径为目标的PDF图形上下文,这里我们pathPDF路径下已经得到一个名叫绘制.pdf文件,不过没有内容, 如果原本就有个叫绘制的pdf文件, 这个文件会被覆盖
    UIGraphicsBeginPDFContextToFile(pathPDF, CGRectZero, NULL);
    // 在PDF上下文中标记新页面的开始,PDF文档是分页的,开启一页文档绘制调用这个方法一次,也可以用UIGraphicsBeginPDFPageWithInfo(CGRect bounds, NSDictionary * __nullable pageInfo)指定PDF的大小,方便绘制
    UIGraphicsBeginPDFPage();
    
    /*
    * 绘制内容的代码,具体下面会说
    */
    
    // 关闭PDF图形上下文并从当前上下文堆栈中弹出它, 不写这段代码, PDF文件打不开
    UIGraphicsEndPDFContext();
    
    

    2. 用UIGraphicsBeginPDFContextToData方法创建

    NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *pathPDF =  [documentPath stringByAppendingPathComponent:@"绘制.pdf"];
    NSMutableData *data = [NSMutableData data];
    UIGraphicsBeginPDFContextToData(data, CGRectZero, NULL);
    
    UIGraphicsBeginPDFPage();
    
    /*
    * 绘制内容的代码,具体下面会说
    */
    
    UIGraphicsEndPDFContext();
    
    [data writeToFile:pathPDF atomically:YES];
    

    两种方法创建PDF, 并在PDF上面绘制文本内容, 如果没有指定创建方法(UIGraphicsBeginPDFPageWithInfoUIGraphicsBeginPDFContextToData)和开始页面的方法UIGraphicsBeginPDFPageWithInfo指定PDF页面的大小, 默认的大小是CGSizeMake(612, 792). 其次, 调用一次UIGraphicsBeginPDFPageUIGraphicsBeginPDFPageWithInfo就会开始新的一页.
    经过我数次的试验, 如果要对原有的PDF进行再次绘制用UIGraphics是不行的, 假如第一种创建PDF代码的那个pathPDF路径下已有一个有内容的绘制.pdf, 但是当我调用UIGraphicsBeginPDFContextToFile方法的时候,这个PDF会被清空, 而第二种创建方法的data, 我传的是一个已有内容的PDF的data数据进去, 那个PDF的内容是可以读取到新的PDF上, 但是无法对其进行绘制, 具体我猜测是当前的上下文和那个PDF的上下文不一样, 应该要先push到那个PDF的上下文, 在进行绘制, 但是那个PDF的上下文我好像无法获取. 所以, 如果是对已有的PDF进行绘制只能用CoreGraphics.

    CoreGraphics

    1. 创建PDF

    • 通过url创建PDF文件

        NSString *docsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    
        NSString *pathPDF = [NSString stringWithFormat:@"%@/绘制.pdf",docsDirectory];
    
        const char *filename = [pathPDF UTF8String];
    
        CFStringRef path = CFStringCreateWithCString (NULL, filename, kCFStringEncodingUTF8);
        CFURLRef url = CFURLCreateWithFileSystemPath (NULL, path, kCFURLPOSIXPathStyle, 0);
        // 释放
        CFRelease(path);
        
        // 创建基于URL的PDF图形上下文
        CGContextRef pdfContext = CGPDFContextCreateWithURL(url, NULL, NULL);
    
        // 释放
        CFRelease(url);
    
        // 指定生成PDF的大小
        CGRect pageRect = CGRectMake(0, 0, 1240, 1754);
        
        // 使指定的图形上下文成为当前上下文
        UIGraphicsPushContext(pdfContext);
        
        // 在基于页面的图形上下文中启动新页面
        CGContextBeginPage(pdfContext, &pageRect);
        
        // 坐标系向上平移了pageRect.size.height, 具体原因下面会解释
        CGContextTranslateCTM(pdfContext, 0, pageRect.size.height);
        // y轴的缩放因子是-1, y乘缩放因子-1, x轴不变, y轴就是沿x轴翻转了过来
        CGContextScaleCTM(pdfContext, 1.0, -1.0);
    
        /*
        * 绘制内容的代码,具体下面会说
        */
        
        // 在PDF图形上下文中结束当前页
        CGPDFContextEndPage(pdfContext);
        // 从堆栈顶部移除当前图形上下文,恢复以前的上下文
        UIGraphicsPopContext();
        // 关闭pdf文档
        CGPDFContextClose(pdfContext);
        
        // 释放
        CGContextRelease(pdfContext);
    

    首先, 上面的pageRectUIGraphics创建PDF时指定的那个大小是一样的, 是相对大小. 这边测试从Word和Page导出的PDF文件大小分别是width = 595.27560000000005 height = 841.88980000000004, width = 595.27999999999997, height = 841.88999999999999
    打印出来纸张的大小虽然都是一样的, 但是PDF的大小不一定一样

    其次, CGContextBeginPageCGPDFContextEndPage成对出现的, 不同于UIGraphics, 开启下一页绘制只需要UIGraphicsBeginPDFPageUIGraphicsBeginPDFPageWithInfo, CoreGraphics开启新一页要同时写CGContextBeginPageCGPDFContextEndPage

    最后说下为什么绘制内容要平移后再翻转, 是因为UIKit的坐标y轴和CoreGraphics的坐标y轴是相反方向的。通常来说,UIKit坐标系相对于屏幕,x轴是左到右递增,y是上到下递增,这样相反CoreGraphicsy就是从下到上递增,两者之间的y轴是相反的,如果CoreGraphics坐标的y轴是相对于屏幕是上到下递增,那UIKit的就是下到上递增,具体方向要相对于屏幕。刚开始用UIGraphics创建出来的PDF的坐标系就是相对于屏幕的x轴是左到右递增,y是上到下递增,即↓→,而绘制的方法也是UIKit的方法,所以是没问题的,如果这时我用CoreText的方法如CTFrameDraw去绘制文字就是会上下颠倒。而当我用CoreGraphics的方法创建PDF,其实它相对于屏幕的x轴也是左到右递增,y是上到下递增,即↓→,如果绘制文字用CTFrameDraw这种CoreText方法是不会上下颠倒的,由于我用的是UIKit的方法,所以绘制出来的内容会颠倒。这两个方法CGContextTranslateCTMCGContextScaleCTM都是相对于CoreGraphics的坐标系来说的,CGContextTranslateCTM(CGContextRef cg_nullable c, CGFloat tx, CGFloat ty)的ty如果是正值就是原来的y坐标减少了ty,由于PDF的坐标系相对于屏幕是↓→,所以CGContextTranslateCTM(pdfContext, 0, pageRect.size.height);其实是向上移动了pageRect.size.height。如果用UIGraphics创建出来的PDF调用CGContextTranslateCTM(pdfContext, 0, pageRect.size.height);,此时的CoreGraphics的坐标是↑→,y轴减少了pageRect.size.height,所以相对于屏幕是向下移动。同理CGContextScaleCTM(CGContextRef cg_nullable c, CGFloat sx, CGFloat sy)缩放,如果sy是-1,就是将内容沿x轴翻转过来,-2翻转过来再拉伸两倍,当然这也是相对于CoreGraphics的坐标系。如果不进行这个操作绘制出来的PDF会变成下面这样

    • 通过data写入的方式创建PDF文件

        NSMutableData *pdfData = [[NSMutableData alloc] init];
        CGDataConsumerRef dataConsumer = CGDataConsumerCreateWithCFData((CFMutableDataRef)pdfData);
        CGContextRef pdfContext = CGPDFContextCreate(dataConsumer, NULL, NULL);
        
        CGDataConsumerRelease(dataConsumer);
        
        CGRect pageRect = CGRectMake(0.0f, 0.0f, 1240, 1754);
        
        UIGraphicsPushContext(pdfContext);
        CGContextBeginPage(pdfContext, &pageRect);
    
        CGContextTranslateCTM(pdfContext, 0, pageRect.size.height);
        CGContextScaleCTM(pdfContext, 1.0, -1.0);
        
        /*
        * 绘制内容的代码,具体下面会说
        */
    
        CGContextEndPage(pdfContext);
        CGPDFContextClose(pdfContext);
        UIGraphicsPopContext();
    
        CGContextRelease(pdfContext);
        
        NSString *docsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    
        NSString *pathPDF = [NSString stringWithFormat:@"%@/绘制.pdf",docsDirectory];
        
        [pdfData writeToFile:pathPDF atomically:YES];
    

    这个和第一种创建PDF不同在创建PDF上下文的方式不同, 而且最后要自己写入文件

    2. 绘制原有PDF文件

        NSString *docsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    
        NSString *pathPDF = [NSString stringWithFormat:@"%@/绘制.pdf",docsDirectory];
    
        NSString *pathName = [[NSBundle mainBundle] pathForResource:@"模板" ofType:@"pdf"];
    
        const char *filename = [pathPDF UTF8String];
        const char *bgFilename = [pathName UTF8String];
    
        CFStringRef path = CFStringCreateWithCString (NULL, filename, kCFStringEncodingUTF8);
        CFURLRef url = CFURLCreateWithFileSystemPath (NULL, path, kCFURLPOSIXPathStyle, 0);
        CFRelease(path);
        
        CFStringRef bgPath = CFStringCreateWithCString (NULL, bgFilename, kCFStringEncodingUTF8);
        CFURLRef bgUrl = CFURLCreateWithFileSystemPath (NULL, bgPath, kCFURLPOSIXPathStyle, 0);
        CFRelease(bgPath);
    
        // 创建基于URL的PDF图形上下文
        CGContextRef pdfContext = CGPDFContextCreateWithURL(url, NULL, NULL);
        CFRelease(url);
    
        // 使用URL指定的数据创建核心图形PDF文档。获取模板这个PDF文件
        CGPDFDocumentRef document = CGPDFDocumentCreateWithURL(bgUrl);
        // document也可以用data创建
    //    NSData *pdfData = [NSData dataWithContentsOfFile:pathName];
    //    CFDataRef myPDFData = (__bridge CFDataRef) pdfData;
    //    CGDataProviderRef provider = CGDataProviderCreateWithCFData(myPDFData);
    //    CGPDFDocumentRef document = CGPDFDocumentCreateWithProvider(provider);
    //    CGDataProviderRelease(provider);
        CFRelease(bgUrl);
        
        // 返回核心图形PDF文档中的页面。获取这个文档的第一页
        CGPDFPageRef page = CGPDFPageRetain(CGPDFDocumentGetPage(document, 1));
        
        // 获取原有PDF文件page页面的大小
        CGRect pageRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
        
        // 使指定的图形上下文成为当前上下文
        UIGraphicsPushContext(pdfContext);
        
        // 在基于页面的图形上下文中启动新页面
        CGContextBeginPage(pdfContext, &pageRect);
        
        // 将PDF页面的内容绘制到当前图形上下文中
        CGContextDrawPDFPage(pdfContext, page);
        
        // 坐标系向上平移了pageRect.size.height, 具体原因下面会解释
        CGContextTranslateCTM(pdfContext, 0, pageRect.size.height);
        // y轴的缩放因子是-1, y乘缩放因子-1, x轴不变, y轴就是沿x轴翻转了过来
        CGContextScaleCTM(pdfContext, 1.0, -1.0);
    
        /*
        * 绘制内容的代码,具体下面会说
        */
        
        // 在PDF图形上下文中结束当前页
        CGPDFContextEndPage(pdfContext);
        // 从堆栈顶部移除当前图形上下文,恢复以前的上下文
        UIGraphicsPopContext();
        // 关闭pdf文档
        CGPDFContextClose(pdfContext);
        
        
        CGPDFDocumentRelease(document);
        CGContextRelease(pdfContext);
    

    上面代码大概就是将模板.pdf的第一页内容绘制到绘制.pdf上, 如果还要在上面绘制, 可以将代码添加到绘制内容的代码的注释那里.
    这里要注意的是CGPDFPageGetBoxRect()方法, 是获取传进去page页面的大小, 最好以这个大小去绘制内容, 假如有这么个需求, 就是给你一个试题的PDF, 要你在这个试题里面绘制内容, 比如填空, 但打印出来的时候只打印你的答案, 打印用的A4纸已经提前把试题打印上去了, 你再用着纸打印你的答案, 如果两个PDF不是一样大, 那么你打印出来的答案可能会落不到原本的空里面(PS:内容的frame是一次次试出来的)
    只要用好CGPDFDocumentRefCGPDFPageRef基本上可以进行很多操作, 又例如合并两个pdf, 核心代码如下

    NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    
    // File paths
    NSString *pdfPath1 = [[NSBundle mainBundle] pathForResource:@"合并1" ofType:@"pdf"];
    NSString *pdfPath2 = [cacheDir stringByAppendingPathComponent:@"合并2.pdf"];
    NSString *pdfPathOutput = [cacheDir stringByAppendingPathComponent:@"out.pdf"];
    
    // File URLs - bridge casting for ARC
    CFURLRef pdfURL1 = (__bridge_retained CFURLRef)[[NSURL alloc] initFileURLWithPath:(NSString *)pdfPath1];//(CFURLRef) NSURL
    CFURLRef pdfURL2 = (__bridge_retained CFURLRef)[[NSURL alloc] initFileURLWithPath:(NSString *)pdfPath2];//(CFURLRef)
    CFURLRef pdfURLOutput =(__bridge_retained CFURLRef) [[NSURL alloc] initFileURLWithPath:(NSString *)pdfPathOutput];//(CFURLRef)
    
    // File references
    CGPDFDocumentRef pdfRef1 = CGPDFDocumentCreateWithURL((CFURLRef) pdfURL1);
    CGPDFDocumentRef pdfRef2 = CGPDFDocumentCreateWithURL((CFURLRef) pdfURL2);
    
    // Number of pages
    NSInteger numberOfPages1 = CGPDFDocumentGetNumberOfPages(pdfRef1);
    NSInteger numberOfPages2 = CGPDFDocumentGetNumberOfPages(pdfRef2);
    
    // Create the output context
    CGContextRef writeContext = CGPDFContextCreateWithURL(pdfURLOutput, NULL, NULL);
    
    // Loop variables
    CGPDFPageRef page;
    CGRect mediaBox;
    
    // Read the first PDF and generate the output pages
    NSLog(@"GENERATING PAGES FROM PDF 1 (%i)...", numberOfPages1);
    // 将第一个PDF, 一页页CGContextDrawPDFPage到out.pdf上
    for (int i=1; i<=numberOfPages1; i++) {
        page = CGPDFDocumentGetPage(pdfRef1, i);
        mediaBox = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
        CGContextBeginPage(writeContext, &mediaBox);
        CGContextDrawPDFPage(writeContext, page);
        CGContextEndPage(writeContext);
    }
    
    // Read the second PDF and generate the output pages
    NSLog(@"GENERATING PAGES FROM PDF 2 (%i)...", numberOfPages2);
    // 再将第二个PDF, 一页页CGContextDrawPDFPage到out.pdf上
    for (int i=1; i<=numberOfPages2; i++) {
        page = CGPDFDocumentGetPage(pdfRef2, i);
        mediaBox = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
        CGContextBeginPage(writeContext, &mediaBox);
        CGContextDrawPDFPage(writeContext, page);
        CGContextEndPage(writeContext);      
    }
    NSLog(@"DONE!");
    
    // Finalize the output file
    CGPDFContextClose(writeContext);
    
    // Release from memory
    CFRelease(pdfURL1);
    CFRelease(pdfURL2);
    CFRelease(pdfURLOutput);
    CGPDFDocumentRelease(pdfRef1);
    CGPDFDocumentRelease(pdfRef2);
    CGContextRelease(writeContext);
    

    二、绘制内容

    主要是文字,下划线,外边框的绘制

    • 导入第三方字体
      系统默认的中文字体是苹方,不是Helvetica,默认的英文字体才是Helvetica,有些文章说默认字体字体是Helvetica,我打开Mac自带的字体册看到Helvetica根本不支持中文。使用其他字体可以去字体册找,右键去Finder位置找到ttc或者ttf的文件,导入工程,导入的时候要勾选Add to targets,还要在info.plist文件里面配置的。具体如下图


      字体册有些有的,有些有但是无法前往Finder里面找到文件,可能是版权问题。
      注意:如果发现字体没什么变化,可能是fontName搞错了,fontName不是ttc/ttf文件名,比如Songti.ttc的文件,一个文件包含了三种字体,也就是三个fontName:STSong(华文宋体)、STSongti-SC(宋体-简)、STSongti-TC(宋体-繁),如果实在字体册里面,可以看PostScript名称,实在不知道可以xib或者storyboard里面找个label看下Font属性里面的Custom的Family里面找,或者用代码直接打印出来所有fontName。


    // 打印
     NSArray *fontFamilys = [UIFont familyNames];
     for (NSString *familyName in fontFamilys) {
         NSLog(@"family name : %@",familyName);
         NSArray *fontNames = [UIFont fontNamesForFamilyName:familyName];
         for (NSString *fontName in fontNames) {
             NSLog(@"font name : %@",fontName);
         }
     }
    
    // 使用
    UIFont *font = [UIFont fontWithName:@"STSong" size:FONT];
    

    字体这块最好用动态加载好一点,因为导入的ttf文件很大,这会导致我们的包变大,可以参考以下代码

    - (void)asynchronouslySetFontName:(NSString *)fontName
    {
        UIFont* aFont = [UIFont fontWithName:fontName size:12.];
    
    /* 
    判断字体是否已经存在, 进入应用后执行CTFontDescriptorMatchFontDescriptorsWithProgressHandler并kCTFontDescriptorMatchingDidFinish, 
    即下载成功了, 未退出应用第二次进去这个方法就会进入下面的条件, 但如果关掉应用进程重新进入, 不会进入下面的条件还是会跑到CTFontDescriptorMatchFontDescriptorsWithProgressHandler回调里面, 
    但不会再去下载, 直接回调的状态是kCTFontDescriptorMatchingDidBegin , kCTFontDescriptorMatchingDidFinishDownloading, 然后kCTFontDescriptorMatchingDidFinish,  
    不会有准备下载和下载中的状态返回, 字体下载一次即使卸载应用也是存在的, 如果当前在下载一种字体,再调用CTFontDescriptorMatchFontDescriptorsWithProgressHandler, 
    要等那种字体下载完了才会进入新调用方法的回调
    */
        if (aFont && ([aFont.fontName compare:fontName] == NSOrderedSame || [aFont.familyName compare:fontName] == NSOrderedSame)) {
            return;
        }
        
        // Create a dictionary with the font's PostScript name.
        NSMutableDictionary *attrs = [NSMutableDictionary dictionaryWithObjectsAndKeys:fontName, kCTFontNameAttribute, nil];
        
        // Create a new font descriptor reference from the attributes dictionary.
        CTFontDescriptorRef desc = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)attrs);
        
        NSMutableArray *descs = [NSMutableArray arrayWithCapacity:0];
        [descs addObject:(__bridge id)desc];
        CFRelease(desc);
        
        __block BOOL errorDuringDownload = NO;
        
    // 文档返回NO会结束下载进程, 但我试了好像没什么卵用, 一旦开始下载字体就停不下来
        CTFontDescriptorMatchFontDescriptorsWithProgressHandler( (__bridge CFArrayRef)descs, NULL,  ^(CTFontDescriptorMatchingState state, CFDictionaryRef progressParameter) {
            
            //NSLog( @"state %d - %@", state, progressParameter);
            // 进度, 100是完成
            double progressValue = [[(__bridge NSDictionary *)progressParameter objectForKey:(id)kCTFontDescriptorMatchingPercentage] doubleValue];
            
            if (state == kCTFontDescriptorMatchingDidBegin) {
                dispatch_async( dispatch_get_main_queue(), ^ {
                    NSLog(@"Begin Matching");
                });
            } else if (state == kCTFontDescriptorMatchingDidFinish) {
                dispatch_async( dispatch_get_main_queue(), ^ {
                    if (!errorDuringDownload) {
                        NSLog(@"%@ downloaded", fontName);
                    }
                });
            } else if (state == kCTFontDescriptorMatchingWillBeginDownloading) {
                dispatch_async( dispatch_get_main_queue(), ^ {
                    NSLog(@"Begin Downloading");
                });
            } else if (state == kCTFontDescriptorMatchingDidFinishDownloading) {
                dispatch_async( dispatch_get_main_queue(), ^ {
                    NSLog(@"Finish downloading");
                });
            } else if (state == kCTFontDescriptorMatchingDownloading) {
                dispatch_async( dispatch_get_main_queue(), ^ {
                    NSLog(@"Downloading %.0f%% complete", progressValue);
                });
            } else if (state == kCTFontDescriptorMatchingDidFailWithError) {
                // An error has occurred.
                // Get the error message
                NSError *error = [(__bridge NSDictionary *)progressParameter objectForKey:(id)kCTFontDescriptorMatchingError];
                if (error != nil) {
                    _errorMessage = [error description];
                } else {
                    _errorMessage = @"ERROR MESSAGE IS NOT AVAILABLE!";
                }
                // Set our flag
                errorDuringDownload = YES;
                
                dispatch_async( dispatch_get_main_queue(), ^ {
                    NSLog(@"Download error: %@", _errorMessage);
                });
            }
            
            return (bool)YES;
        });
        
    }
    
    • 绘制文字
      由于我这边绘制的内容绘制的文字只要分为三种,一种是标题的黑体加粗苹方,一种是正文的宋体,还有一种是表格里面宋体,由于宽度有限,会换行,行间距要指定大小,所以进行了如下封装
    /**
     *  @brief 文字,系统默认的中文字体是苹方,默认的英文字体是Helvetica,可以指定其他中文字体
     *
     *  @param printStr 绘制的文字
     *  @param rect 文字frame
     *  @param fontName 文字字体
     *  @param lineSpacing 文字行间距
     *  @param lineBreakMode 文字过长时的显示格式
     *  @param alignment 文字对齐方式
     */
    - (void)printStr:(NSString *)printStr
              CGRect:(CGRect)rect
                Font:(CGFloat)font
        fontWithName:(NSString *)fontName
         lineSpacing:(CGFloat)lineSpacing
       lineBreakMode:(NSLineBreakMode)lineBreakMode
           alignment:(NSTextAlignment)alignment;
    /**
     *  @brief 文字,默认宋体,系统默认行间距
     *
     *  @param printStr 绘制的文字
     *  @param rect 文字frame
     *  @param lineBreakMode 文字过长时的显示格式
     *  @param alignment 文字对齐方式
     */
    - (void)printStr:(NSString *)printStr
              CGRect:(CGRect)rect
                Font:(CGFloat)font
       lineBreakMode:(NSLineBreakMode)lineBreakMode
           alignment:(NSTextAlignment)alignment;
    /**
     *  @brief 文字,默认宋体
     *
     *  @param printStr 绘制的文字
     *  @param rect 文字frame
     *  @param lineSpacing 文字行间距
     *  @param lineBreakMode 文字过长时的显示格式
     *  @param alignment 文字对齐方式
     */
    - (void)printStr:(NSString *)printStr
              CGRect:(CGRect)rect
                Font:(CGFloat)font
         lineSpacing:(CGFloat)lineSpacing
       lineBreakMode:(NSLineBreakMode)lineBreakMode
           alignment:(NSTextAlignment)alignment;
    
    - (void)printStr:(NSString *)printStr
              CGRect:(CGRect)rect
                Font:(CGFloat)font
        fontWithName:(NSString *)fontName
         lineSpacing:(CGFloat)lineSpacing
       lineBreakMode:(NSLineBreakMode)lineBreakMode
           alignment:(NSTextAlignment)alignment
    {
        UIFont *Font = [UIFont fontWithName:fontName size:font];
        NSMutableParagraphStyle* paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
        paragraphStyle.lineBreakMode = lineBreakMode;
        paragraphStyle.alignment = alignment;
        if (lineSpacing > 0) {
            paragraphStyle.lineSpacing = lineSpacing;
        }    NSDictionary *attribute = @{
                                   NSFontAttributeName:Font,
                                   NSParagraphStyleAttributeName:paragraphStyle
                                   };
        [printStr drawWithRect:rect options:NSStringDrawingUsesLineFragmentOrigin attributes:attribute context:nil];
    }
    
    - (void)printStr:(NSString *)printStr
              CGRect:(CGRect)rect
                Font:(CGFloat)font
       lineBreakMode:(NSLineBreakMode)lineBreakMode
           alignment:(NSTextAlignment)alignment
    {
        [self printStr:printStr CGRect:rect Font:font fontWithName:@"STSong" lineSpacing:0.0f lineBreakMode:lineBreakMode alignment:alignment];
    
    }
    
    
    - (void)printStr:(NSString *)printStr
              CGRect:(CGRect)rect
                Font:(CGFloat)font
         lineSpacing:(CGFloat)lineSpacing
       lineBreakMode:(NSLineBreakMode)lineBreakMode
           alignment:(NSTextAlignment)alignment
    {
        [self printStr:printStr CGRect:rect Font:font fontWithName:@"STSong" lineSpacing:lineSpacing lineBreakMode:lineBreakMode alignment:alignment];
    }
    
    • 绘制横线
    /**
     *  @brief 两点坐标相连画线,高度1像素
     *
     *  @param from 起始坐标
     *  @param to 结束坐标
     */
    - (void)drawLineFromPoint:(CGPoint)from toPoint:(CGPoint)to;
    
    - (void)drawLineFromPoint:(CGPoint)from toPoint:(CGPoint)to
    {
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
        CGContextSetLineWidth(context, 1.0);
        CGContextMoveToPoint(context, from.x, from.y);
        CGContextAddLineToPoint(context, to.x, to.y);
        CGContextStrokePath(context);
    }
    
    • 绘制外边框
    /**
     *  @brief 空心矩形
     *
     *  @param rect 空心矩形的frame
     */
    - (void)drawEmptySquareWithCGRect:(CGRect)rect;
    
    - (void)drawEmptySquareWithCGRect:(CGRect)rect
    {
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGFloat components[] = {10,5};
        CGContextSetLineDash(context, 0, components, 0);
        CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
        CGContextSetLineWidth(context, 1.0);
        CGContextAddRect(context, rect);
        CGContextStrokePath(context);
    }
    

    之后的具体代码就是不断的绘制文字,下滑线的过程,具体可以看下面的Demo

    三、展示

    
    /*
    * PDF文档预览的几种方式
    * UIWebView 
    * QLPreviewController
    * UIDocumentInteractionController
    * CGContexDrawPDFPage
    * 具体怎么使用可以移步https://www.jianshu.com/p/95168c23fb39
    */
    // 将绘制好的PDF文件展示到UIWebView
    UIWebView *webView  = [[UIWebView alloc]initWithFrame:self.view.bounds];
    webView.scalesPageToFit = YES;
    NSURL *url = [NSURL fileURLWithPath:pathPDF];  
    [self.webView loadRequest:[NSURLRequest requestWithURL:url]];
    [self.view addSubview webView];
    
    
    效果图

    四、隔空打印

    • 安装打印机模拟器
      当然也可以用真的打印机,为了方便开发,可以安装一个打印机模拟器,拉下去找一下,Hardware那个,点开点右边下载即可


      之后直接打开Printer Simulator就行了,不用用蓝牙连接什么的,iPhone或iPad弹出的苹果的打印页面会看到这个打印机模拟器,选择他们进行打印就可以模拟打印这个过程,当然他不会真的凭空变出一直纸给你
    • UIPrintInteractionController

    打印界面弹窗,用于管理iOS中文档,图像和其他可打印内容的类。就是隔空打印,就是用这个类搞掂的。

    1. 可打印的内容
    an array of ready-to-print images and PDF documents: 一组图片文件和PDF文件。
    a single image or PDF document: 一张图片或是一个PDF文件。、
    an instance of any of the built-in print formatter classes: 打印格式化者的实例。(简单文本,html文档,某些View显示的内容)。
    a custom page renderer: 自定义页渲染者。
    

    简单的说就是可以打印web页面,PDF,图片等等

    1. UIPrintInteractionController 的属性、方法
    @property(nullable, nonatomic, strong) UIPrintInfo *printInfo;      // 打印任务的信息,其中包括输出类型, 打印方向, 打印文件名称等等
    @property(nullable, nonatomic, weak)   id<UIPrintInteractionControllerDelegate> delegate;  // 代理
    @property(nonatomic) BOOL showsPageRange // 显示页码,废弃了,现在默认都显示
    @property(nonatomic) BOOL showsNumberOfCopies // 打印弹窗是否显示打印份数,默认是
    
    /*
    * 以下四个属性都是用来指定打印的内容
    */
    @property(nullable, nonatomic, strong) UIPrintPageRenderer *printPageRenderer;  // 根据内容的种类来布置页面内容的对象,直译
    @property(nullable, nonatomic, strong) UIPrintFormatter    *printFormatter;     // 当UIKit请求时可绘制可打印内容页面的对象,直译
    @property(nullable, nonatomic, copy) id printingItem;             // 单个准备打印 NSData, NSURL, UIImage 对象
    @property(nullable, nonatomic, copy) NSArray *printingItems;      // 一组准备打印的 NSData, NSURL, UIImage对象
    
    /*
    * 确定可印刷性
    */
    @property(class, nonatomic, readonly, getter=isPrintingAvailable) BOOL printingAvailable;   // 设备是否支持打印
    + (BOOL)canPrintURL:(NSURL *)url;      // NSURL对象引用的文件是否可以打印
    + (BOOL)canPrintData:(NSData *)data;  // NSData对象是否可以打印
    
    /*
    * 弹出弹窗界面的方法
    */
    - (BOOL)presentAnimated:(BOOL)animated completionHandler:(nullable UIPrintInteractionCompletionHandler)completion;  // 官方虽然说这是iPhone用的方法,但我iPad调用这个方法也会弹窗
    - (BOOL)presentFromRect:(CGRect)rect inView:(UIView *)view animated:(BOOL)animated completionHandler:(nullable UIPrintInteractionCompletionHandler)completion;    // iPad,在控制器上直接传self.view.frame, self.view即可
    - (BOOL)presentFromBarButtonItem:(UIBarButtonItem *)item animated:(BOOL)animated completionHandler:(nullable UIPrintInteractionCompletionHandler)completion;      // iPad
    - (void)dismissAnimated:(BOOL)animated;  // 退出打印选项表或弹出框
    
    
    1. UIPrintInfo的属性
    @property(nonatomic, copy) NSString *jobName;           // 打印文件名称,默认工程名字
    @property(nonatomic) UIPrintInfoOutputType outputType;        // 可打印内容的种类,默认 UIPrintInfoOutputGeneral
    @property(nonatomic) UIPrintInfoOrientation orientation;       // 打印内容的方向(纵向或横向),默认UIPrintInfoOrientationPortrait
    @property(nonatomic) UIPrintInfoDuplex duplex;            // 用于打印作业的双面模式
    
    所有API
    1. 默认弹窗界面


      iPad
      iPhone

      Select Printer选择打印机里面有好几个都是模拟器生成的


    1. 代码实现
      只是打印PDF的代码
        UIPrintInteractionController *pic = [UIPrintInteractionController sharedPrintController];
        NSData *myPDFData = [NSData dataWithContentsOfFile:self.pathPDF];
        if (pic && [UIPrintInteractionController canPrintData:myPDFData]) {
            UIPrintInfo *printInfo = [UIPrintInfo printInfo];
            printInfo.outputType = UIPrintInfoOutputGeneral;
            printInfo.jobName = @"绘制.pdf";
            printInfo.duplex = UIPrintInfoDuplexLongEdge;
    
            pic.printInfo = printInfo;
            pic.printingItem = myPDFData;
            
            void (^completionHandler)(UIPrintInteractionController *, BOOL, NSError *) = ^(UIPrintInteractionController *pic, BOOL completed, NSError *error) {
                if (!completed && error) NSLog(@"FAILED! due to error in domain %@ with error code %ld",error.domain, (long)error.code);
            };
    
            if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    //            [pic presentFromRect:self.view.frame inView:self.view animated:YES completionHandler:completionHandler];
                [pic presentFromBarButtonItem:sender animated:YES completionHandler:completionHandler];
            } else {
                //直接打印
                [pic presentAnimated:YES completionHandler:completionHandler];
            }
        }
    

    Demo地址

    参考:
    https://www.jianshu.com/p/7cff5d89f3ac
    https://www.jianshu.com/p/95168c23fb39
    https://github.com/billzbh/PNCPDFTable
    https://www.coder.work/article/467182

    相关文章

      网友评论

        本文标题:iOS 绘制PDF页面并隔空打印出来

        本文链接:https://www.haomeiwen.com/subject/oljdzktx.html