iOS中如何使用HTML进行打印

iOS中如何使用HTML进行打印

@(iOS)[HTML]

新需求,要把app中的信息打印出来,通过AirPrint方式打印,一脸懵逼。
听说图片可以打印,但是要把信息绘制成指定格式的图片好坑,灵活性太低。
听说用textKit可以实现,但是textKit图文混排是个大坑,还是想想其他方法。
又听说PDF可以直接打印,这个也需要手动绘制出来,其实和图片相差不多。
最终我选择使用html,html也可以直接打印,主要是这个排版比较容易。

iOS中用于打印的类

  • 打印过程
  • 苹果有专门的类来处理打印相关的信息。这个类就是UIPrintPageRenderer,可以设置页眉页脚边距纸张大小等等。
  • 参考官方文档

重写UIPrintPageRenderer

  • 里面有个关键类就是我们重写的UIPrintPageRenderer

  • 因为需要显示页眉和页脚,所以要自定义SLQPrintPageRenderer

  • 初始化打印器,指定纸张大小,以及页眉页脚高度和内边距

- (instancetype)init {
    if (self = [super init]) {
        CGRect rect = CGRectMake(0, 0, self.A4PageWidth, self.A4PageHeight);
        // 纸张大小
        [self setValue:[NSValue valueWithCGRect:rect] forKey:@"paperRect"];
        
        // 打印区域,如果需要间距就这样CGRectInset(pageFrame, 10.0, 10.0))
//        [self setValue:[NSValue valueWithCGRect:rect] forKey:@"printableRect"];
        [self setValue:[NSValue valueWithCGRect:CGRectInset(rect, 10, 10)] forKey:@"printableRect"];
        // 页眉页脚
        self.headerHeight = 50.0;
        self.footerHeight = 50.0;
    }
    return self;
}
  • 重写页数,

/*
 Override numberOfPages so we can compute the values for our UIPrintFormatter based on the paper used for the print job. When this is called, self.paperRect and self.printableRect reflect the paper size and imageable area of the destination paper.
 重写改方法,计算页数
 */
- (NSInteger)numberOfPages
{
    // We only have one formatter so obtain it so we can set its paramters.
    UIPrintFormatter *myFormatter = (UIPrintFormatter *)[self.printFormatters objectAtIndex:0];
    
    /*
     Compute insets so that margins are 1/2 inch from edge of sheet, or at the edge of the imageable area if it is larger than that. The EdgeInset function takes a margin for the edge being calculated.
     */
    CGFloat leftInset = EdgeInset(self.printableRect.origin.x);
    CGFloat rightInset = EdgeInset(self.paperRect.size.width - CGRectGetMaxX(self.printableRect));
    
    // Top inset is only used if we want a different inset for the first page and we don't.
    // The bottom inset is never used by a viewFormatter.
    myFormatter.contentInsets = UIEdgeInsetsMake(0, leftInset, 0, rightInset);
    
    // Now compute what we want for the header size and footer size.
    // These determine the size and placement of the content height.
    
    // First compute the title height.
    UIFont *font = [UIFont fontWithName:@"Helvetica" size:HEADER_FOOTER_TEXT_HEIGHT];
    // We'll use the same title height for the header and footer.
    // This is the minimum height the footer can be.
    CGFloat titleHeight = [@"询问笔录" sizeWithFont:font].height;
    
    /*
     We want to calculate these heights so that the content top and bottom edges are a minimum distance from the edge of the sheet and are inset at least MIN_HEADER_FOOTER_DISTANCE_FROM_CONTENT from the header and footer.
     */
    self.headerHeight = HeaderFooterHeight(CGRectGetMinY(self.printableRect), titleHeight);
    self.footerHeight = HeaderFooterHeight(self.paperRect.size.height - CGRectGetMaxY(self.printableRect), titleHeight);
    
    // Just to be sure, never allow the content to go past our minimum margins for the content area.
    myFormatter.maximumContentWidth = self.paperRect.size.width - 2*MIN_MARGIN;
    myFormatter.maximumContentHeight = self.paperRect.size.height - 2*MIN_MARGIN;
    
    
    /*
     Let the superclass calculate the total number of pages. Since this UIPrintPageRenderer only uses a UIPrintFormatter, the superclass knows the number of pages based on the formatter metrics and the paper/printable rects.
     
     Note that since this code only uses a single print formatter we could just as easily use myFormatter.pageCount to obtain the total number of pages. But it would be more complex than that if we had multiple printformatters for our job so we're using a more general approach here for illustration and it is correct for 1 or more formatters.
     */
    return [super numberOfPages];
}
  • 准备绘制,绘制前会调用这个方法
/*
 Our pages don't have any intrinsic notion of page number; our footer will number the pages so that users know the order. So for us, we will always render the first page printed as page 1, even if the range is n-m. So we track which page in the range is the first index as well as the total length of our range.
 */
- (void)prepareForDrawingPages:(NSRange)range
{
    pageRange = range;
    [super prepareForDrawingPages:range];
}
  • 绘制页眉页脚
// 绘制页眉
- (void)drawHeaderForPageAtIndex:(NSInteger)pageIndex inRect:(CGRect)headerRect {
    // Specify the header text.
    NSString *headerText = @"第1次";
    
    // Set the desired font.
    UIFont *font = [UIFont systemFontOfSize:15];
    // Specify some text attributes we want to apply to the header text.
    NSDictionary *textAttributes = @{NSFontAttributeName:font,NSForegroundColorAttributeName:[UIColor blackColor],NSKernAttributeName:@7.5};
    // Calculate the text size.
    CGSize textSize = [self getTextSize:headerText font:font att:textAttributes];
    // Determine the offset to the right side.
    CGFloat offsetX = 20.0;
    // Specify the point that the text drawing should start from.
    CGFloat pointX = headerRect.size.width - textSize.width - offsetX;
    CGFloat pointY = headerRect.size.height/2 - textSize.height/2;
    // Draw the header text.
    [headerText drawAtPoint:CGPointMake(pointX, pointY) withAttributes:textAttributes];
}
// 绘制页脚
- (void)drawFooterForPageAtIndex:(NSInteger)pageIndex inRect:(CGRect)footerRect {
    NSString *footerText = [NSString stringWithFormat:@"第%lu页共%lu页",
                            pageIndex+1 - pageRange.location, (unsigned long)pageRange.length];
    NSDictionary *textAttributes = @{NSFontAttributeName:[UIFont systemFontOfSize: 15] ,NSForegroundColorAttributeName:[UIColor blackColor],NSKernAttributeName:@7.5};
    CGSize textSize = [self getTextSize:footerText font:[UIFont systemFontOfSize:15] att:textAttributes];
    
//    CGFloat centerX = footerRect.size.width/2 - textSize.width/2;
//    CGFloat centerY = footerRect.origin.y + self.footerHeight/2 - textSize.height/2;
//
    // Specify the point that the text drawing should start from.
    CGFloat pointX = footerRect.size.width - textSize.width;
    CGFloat pointY = footerRect.origin.y + self.footerHeight/2;
    [footerText drawAtPoint:CGPointMake(pointX, pointY) withAttributes:textAttributes];
    
    // Draw a horizontal line.
    CGFloat lineOffsetX = 20.0;
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetRGBStrokeColor(context, 205.0/255.0, 205.0/255.0, 205.0/255, 1.0);
    CGContextMoveToPoint(context, lineOffsetX, footerRect.origin.y);
    CGContextAddLineToPoint(context, footerRect.size.width - lineOffsetX, footerRect.origin.y);
    CGContextStrokePath(context);
    
}
// 计算字符串尺寸
- (CGSize )getTextSize:(NSString *)text font:(UIFont *)font att:(NSDictionary *)att {

    UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.paperRect.size.width, self.footerHeight)];
    if (att) {
        
        testLabel.attributedText = [[NSAttributedString alloc] initWithString:text attributes:att];
    }else {
        testLabel.text = text;
        testLabel.font = font;
    }
    [testLabel sizeToFit];
    
    return testLabel.frame.size;
}
  • 其他方法
/*
 Compute an edge inset to produce the minimum margin based on the imageable area margin of the edge.
 */
static inline CGFloat EdgeInset(CGFloat imageableAreaMargin)
{
    /*
     Because the offsets specified to a print formatter are relative to printRect and we want our edges to be at least MIN_MARGIN from the edge of the sheet of paper, here we compute the necessary offset to achieve our margin. If the imageable area margin is larger than our MIN_MARGIN, we return an offset of zero which means that the imageable area margin will be used.
     */
    CGFloat val = MIN_MARGIN - imageableAreaMargin;
    return val > 0 ? val : 0;
}


/*
 Compute a height for the header or footer, based on the margin for the edge in question and the height of the text being drawn.
 */
static CGFloat HeaderFooterHeight(CGFloat imageableAreaMargin, CGFloat textHeight)
{
    /*
     Make the header and footer height provide for a minimum margin of MIN_MARGIN. We want the content to appear at least MIN_HEADER_FOOTER_DISTANCE_FROM_CONTENT from the header/footer text. If that requires a margin > MIN_MARGIN then we'll use that. Remember, the header/footer height returned needs to be relative to the edge of the imageable area.
     */
    CGFloat headerFooterHeight = imageableAreaMargin + textHeight +
    MIN_HEADER_FOOTER_DISTANCE_FROM_CONTENT + HEADER_FOOTER_MARGIN_PADDING;
    if(headerFooterHeight < MIN_MARGIN)
        headerFooterHeight = MIN_MARGIN - imageableAreaMargin;
    else {
        headerFooterHeight -= imageableAreaMargin;
    }
    
    return headerFooterHeight;
}

发送邮件

  • 包含头文件#import <MessageUI/MessageUI.h>

  • 发送邮件

#pragma mark - 设置邮件基本信息
// 设置邮件基本信息
-(void)displayComposerSheet
{
    MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
    picker.mailComposeDelegate = self;
    //设置主题
    [picker setSubject:@"HTMLDemo"];
    
    //设置收件人
    NSArray *toRecipients = [NSArray arrayWithObjects:@"xxxx@163.com",nil];
    
    [picker setToRecipients:toRecipients];
    
    //设置附件为pdf
    NSData *myData = [NSData dataWithContentsOfFile:self.pdfFileName];
    if (myData) {
        [picker addAttachmentData:myData mimeType:@"application/pdf" fileName:@"HTMLDemo"];
    }
    
    // 设置邮件发送内容
    NSString *emailBody = @"哈哈尽快哈就合法进口分哈萨克黄齑淡饭";
    [picker setMessageBody:emailBody isHTML:NO];
    
    //邮件发送的模态窗口
    [self presentViewController:picker animated:YES completion:nil];
}
  • 回调信息
#pragma mark - MFMailComposeViewControllerDelegate
-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
    switch (result)
    {
        case MFMailComposeResultCancelled: //取消
            NSLog(@"MFMailComposeResultCancelled-取消");
            break;
        case MFMailComposeResultSaved: // 保存
            NSLog(@"MFMailComposeResultSaved-保存邮件");
            break;
        case MFMailComposeResultSent: // 发送
            NSLog(@"MFMailComposeResultSent-发送邮件");
            break;
        case MFMailComposeResultFailed: // 尝试保存或发送邮件失败
            NSLog(@"MFMailComposeResultFailed: %@...",[error localizedDescription]);
            break;
    }
    
    // 关闭邮件发送视图
    [self dismissViewControllerAnimated:YES completion:nil];
}

数据传递过程

  • 录入数据->传递数据到html模板->生成html->打印

1、录入数据

  • 这个demo,暂时写死吧,如果需要的话,直接保存为模型,传递模型就行啦。

2、传递数据

  • 将模型传递过来后,要对模型数据进行处理,这里直接使用简单粗暴的方式stringByReplacingOccurrencesOfString: withString: ,读取html后直接替换

3、生成html

  • 生成完毕后加载webView直接预览

4、打印html

  • 直接调用printWebPage方法进行打印

打印html

  • 打印html
  • 想要绘制页眉页脚,以及其他信息,可以重写一些方法。
#pragma mark - 打印html
// 打印html
- (void)printWebPage
{
    UIPrintInteractionController *controller = [UIPrintInteractionController sharedPrintController];
    if(!controller){
        NSLog(@"Couldn't get shared UIPrintInteractionController!");
        return;
    }
    
    UIPrintInteractionCompletionHandler completionHandler =
    ^(UIPrintInteractionController *printController, BOOL completed, NSError *error) {
        if(!completed && error){
            NSLog(@"FAILED! due to error in domain %@ with error code %ld", error.domain, (long)error.code);
        }
    };
    
    
    // 设置打印机的一些默认信息
    UIPrintInfo *printInfo = [UIPrintInfo printInfo];
    // 输出类型
    printInfo.outputType = UIPrintInfoOutputGeneral;
    // 打印队列名称
    printInfo.jobName = @"HtmlDemo";
    // 是否单双面打印
    printInfo.duplex = UIPrintInfoDuplexLongEdge;
    // 设置默认打印信息
    controller.printInfo = printInfo;
    
    // 显示页码范围
    controller.showsPageRange = YES;
    
    // 预览设置
    SLQPrintPageRenderer *myRenderer = [[SLQPrintPageRenderer alloc] init];

    // To draw the content of each page, a UIViewPrintFormatter is used.
    // 生成html格式
    UIViewPrintFormatter *viewFormatter = [self.webView viewPrintFormatter];
    [myRenderer addPrintFormatter:viewFormatter startingAtPageAtIndex:0];
    // 渲染html
    controller.printPageRenderer = myRenderer;
    
    [controller presentAnimated:YES completionHandler:completionHandler];
}

Demo地址
参考文章

原文地址:https://www.cnblogs.com/songliquan/p/12784462.html