Unexpected page breaks when printing UIWebView
Asked Answered
G

3

14

TLDR: If you print UIWebView which contains HTML content consisting of elements with text aligned right / center, resulting document will have pages with unexpectedly large bottom margins.

I came across this issue, and was quite surprised when I could not google anyone with similar problem. I have filed a radar with apple (#20760071) and also created an issue on ResearchKit's GitHub repo, as this affects their ORKHTMLPDFWriter.

AFAIK this also affects all libraries that use UIWebView for converting HTML to PDF, I have tested:

I am wondering if anyone can come up with some workaround.

How to reproduce:

NSMutableString* html = [NSMutableString string];
[html appendString:@"<html><body>"];
for (int i=0; i<200; i++) {
    [html appendString:@"<div align=\"right\">line</div>"];
}
[html appendString:@"</body></html>"];

UIPrintInteractionController* pc = [UIPrintInteractionController sharedPrintController];

UIPrintInfo* printInfo = [UIPrintInfo printInfo];
printInfo.outputType = UIPrintInfoOutputGrayscale;

pc.printInfo = printInfo;
pc.showsPaperSelectionForLoadedPapers = YES;

UIWebView* web = [UIWebView new];
[web loadHTMLString:html baseURL:nil];
pc.printFormatter = web.viewPrintFormatter;

[pc presentAnimated:YES completionHandler:^(UIPrintInteractionController *printInteractionController, BOOL completed, NSError *error) {
    NSLog(@"%d -- %@",completed, error);

}];

You can also clone the project demonstrating this issue in ResearchKit.


Edit - somewhat usable workarounds:

Knowing specific width of the content (e.g. images), one can align it without specifying text alignment, e.g:

Using automatic margins:

<div>
    <div style="width:200px; margin-left:auto; margin-right:0px;">
        content
    </div>
</div>

Floating columns:

<div>
    <div style="width:200px; float:right;">
        content
    </div>
</div>

However, none of the above works for aligning variable length text, which is the main use case I am concerned with.

Gilberte answered 30/4, 2015 at 9:54 Comment(3)
I have filed rdar://16713582 on April 24th 2014 concerning the same problem. As you mentionned, the problem seems to happen when using text-align in the document's CSS. The only solution I could come up with was to avoid text-align by using other means. As an example, you can right align a div within another using margin-left:auto; margin-right:0;.Confusion
@Confusion Workaround you mentioned works only if you specify the width of the aligned div, which is usually not applicable for the text alignment. Or am I missing something?Gilberte
I do not recall the specifics of every workaround, but there are various ways to reproduce text-align and they aren't suited to every situation because of their drawbacks. As an example, the float: right; technique results in a different layout which isn't always appropriate. As I said, I don't have a complete solution, only this limited workaround to avoid the printing problem. Of course, you could also use the printing workarounds only for media print to limit the side-effects as much as possible.Confusion
I
4

Vano,

The solution that I came up with after some testing is adding the following css rules with the text align.

display: -webkit-box;
display: -webkit-flex;
display: flex;
justify-content: flex-end;
-webkit-justify-content: flex-end;
text-align:right;
Idioplasm answered 13/10, 2015 at 14:22 Comment(0)
D
0

My solution to text-align:right

<header>
    <style type="text/css">
        .default-text-align-right {
            text-align: right;
        }
        .alternative-text-align-right {
            position: absolute;
            right: 10px;
        }
    </style> 
</header>
<body>
    <div>
        <div class="default-text-align-right">
            Default Text Align Left
        </div>
        <div>
            <div class="alternative-text-align-right">Alternative Text Align Right</div>
            <div>&nbsp;</div>
        </div>
        <div class="default-text-align-right">
            Default Text Align Left
        </div>  
    </div>      
</body>
Diapause answered 6/7, 2016 at 13:10 Comment(0)
S
-1

I imagine that wouldn't work with aligning variable length text, because the length of the text, obviously, is constantly changing, where your workaround's div attribute has a static width attribute.

This is a job for a string replacement, replacing the divider's width with the paper's size (to allow the margins to fill with text). To get this width, call this:

pc.printPaper.printRect.size.width;

But, in order to replace it, you need to have a delegate there to fix up the HTML code right before the job starts.

Have your delegate be a new class object from type NSObject as a subclass in Xcode's File Creator, and then have your function to fire the printer and render the content be similar to this in your view:

NSMutableString* html = [NSMutableString string];
[html appendString:@"<html><body>"];

for (int i=0; i<200; i++) {
    [html appendString:@"temporary content... "];
}

[html appendString:@"</body></html>"];

UIWebView* web = [UIWebView new];
[web loadHTMLString:html baseURL:nil];


UIPrintInteractionController* pc = [UIPrintInteractionController sharedPrintController];
pc.printFormatter = web.viewPrintFormatter;

UIPrintInfo* printInfo = [UIPrintInfo printInfo];
printInfo.outputType = UIPrintInfoOutputGrayscale;

printerDelegate* pd = [printerDelegate new];

pc.printInfo = printInfo;
pc.showsPaperSelectionForLoadedPapers = YES;
pc.delegate = pd;

[pc presentAnimated:YES completionHandler:^(UIPrintInteractionController *printInteractionController, BOOL completed, NSError *error) {
    NSLog(@"%d -- %@",completed, error);
}];

(Note: I made temporary content in the HTML file for a reason. I'm not done yet!)

In the delegate class I asked you to make before, make this the .h file:

    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>

    @interface printerDelegate : NSObject <UIPrintInteractionControllerDelegate>

    @end

Then, in it's .m file, I have this...

#import "printerDelegate.h"
#import <UIKit/UIKit.h>

@implementation printerDelegate

-(void)printInteractionControllerWillDismissPrinterOptions:(UIPrintInteractionController *)printInteractionController {

    @try {
        NSMutableString* html = [NSMutableString string];
    [html appendString:@"<html><body>"];
    for (int i=0; i<200; i++) {
        [html appendString:[NSString stringWithFormat:@"<div><div style=""width:%fpx; margin-left:auto; margin-right:0px;"">content</div></div>", printInteractionController.printPaper.printRect.size.width]];
    }
    [html appendString:@"</body></html>"];
    UIWebView* web = [UIWebView new];
    [web loadHTMLString:html baseURL:nil];
    printInteractionController.printFormatter = web.viewPrintFormatter;
    }
    @catch (NSException *exception) {
        NSLog(@"%@", exception.debugDescription);
    }
    @finally {

    }
}

@end

It returns a bad access exception, but you get the gist: after the user selects their type of paper, set the divider's width to be the width of the printing area. The only other option would be to either have a specified width (like you suggested in your edit) or have one specific type paper the user can print from.

The reason why this happens is because of a bug in printing the HTML of a UIWebView, not because of coding. Hopefully Apple fixes this in a later update.

Suspect answered 10/5, 2015 at 5:39 Comment(1)
You have described how to set divider's width equal to the width of paper. Well, style="width:100%" achieves the same result, and I fail to see how this relates to the problem at all.Gilberte

© 2022 - 2024 — McMap. All rights reserved.