Rendering PDF in UIWebView iOS 8, causes a black b

2020-01-30 04:29发布

问题:

In iOS 8, when rendering a .PDF into a UIWebview there is a black border and background around the PDF displayed (not the whole background view). Note this is not the UIWebview background which is set to:

myWebView.opaque = NO;
myWebView.backgroundColor = [UIColor clearColor];

This is not present in < iOS8, (no black bordering coloured background around the .PDF)

Anyone else experienced this who could shed some light on this?

Im loading my PDF into the Web view like so..

- (void)viewWillAppear:(BOOL)animated
{

    [super viewWillAppear:animated];
    if (self.pdfData != nil && self.viewHasUnloaded == YES) {
        self.viewHasUnloaded = NO;
        [self.webView loadData:self.pdfData MIMEType:@"application/pdf" textEncodingName:@"utf-8" baseURL:nil];
    }
}

回答1:

Turn off WebKitDiskImageCacheEnabled and the black border goes away:

In applicationDidFinishLaunchingWithOptions add the following lines:

[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"WebKitDiskImageCacheEnabled"];
[[NSUserDefaults standardUserDefaults] synchronize];


回答2:

After a bit of investigating the issue I managed to understand where the problem is. So the UIWebView is an UIView, with an UIScrollView (with private class _UIWebViewScrollView) as subview. The loading of a PDF into the UIWebView goes under the following flow:
1) UIWebView starts loading the request. By this time -webViewDidStartLoad: delegate method is called.
2) After the PDF is (down)loaded the delegate method -webViewDidFinishLoad: is called. By that time the UIWebView knows this is a PDF file and a subview with a private class UIWebPDFView is already inserted into the _UIWebViewScrollView, but the PDF itself is not rendered, yet.
And here comes the problem.
3) The PDF is rendered offscreen and after it's ready a new subview with private class UIPDFPageView is inserted into UIWebPDFView and the PDF is displayed. The problem is when this insertion happens the UIWebPDFView has its backgroundColor set to black and this insertion happens after the -webViewDidFinishLoad: is called (the time depends on how big the PDF is to render). That's why it's not a good solution to go through all subviews of the UIWebView and set their backgroundColor property to white, for example.

The good news is that the UIViewController's method -viewDidLayoutSubviews is called when the UIPDFPageView is inserted into the UIWebView's view hierarchy. So, in the end the solution is to have this objective-C code into our view controller:

-(void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    // Assuming self.webView is our UIWebView
    // We go though all sub views of the UIWebView and set their backgroundColor to white
    UIView *v = self.webView;
    while (v) {
        v.backgroundColor = [UIColor whiteColor];
        v = [v.subviews firstObject];
    }
}

And in Swift:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    var v:UIView? = self.webView
    while (v != nil) {
        v!.backgroundColor = UIColor.whiteColor()
        v = v!.subviews.first
    }
}


回答3:

I assume it's a bit of a timing-thing because UIWebPDFView is added as subview not until the rendering has finished and viewDidLayoutSubviews is called before that happens. And because there is no event like didFinshedRendering i used a timer to check when UIWebPDFView is added.

The following is working for me.

Put a instance variable in the in the header file or your class:

// instance variable for interval timer
NSTimer *BackgroundIntervalTimer;

Put these methods in the implementation file:

- (void)startUIWebViewBackgroundFixTimer {
    // if > iOS 8 start fixing background color of UIWebPDFView
    if (!SYSTEM_VERSION_LESS_THAN(@"8.0")) {
        // hide pdfWebView until background fixed
        self.pdfWebView.alpha = 0.0;
        // start interval timer
        BackgroundIntervalTimer = [NSTimer scheduledTimerWithTimeInterval:0.1  target:self selector:@selector(fixBackground) userInfo:nil repeats:YES];
    }
}

- (void)stopUIWebViewBackgroundFixTimer {
    [BackgroundIntervalTimer invalidate];
    BackgroundIntervalTimer = nil;
}

- (void)fixBackground {
    // stop timer interval
    [self stopUIWebViewBackgroundFixTimer];

    // Assuming self.webView is our UIWebView
    // We go though all sub views of the UIWebView and set their backgroundColor to white
    UIView *v = self.pdfWebView;
    while (v) {
        //v.backgroundColor = [UIColor whiteColor];
        v = [v.subviews firstObject];

        if ([NSStringFromClass([v class]) isEqualToString:@"UIWebPDFView"]) {
            [v setBackgroundColor:[UIColor whiteColor]];

            // background set to white so fade view in and exit
            [UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseOut
                             animations:^{
                                 self.pdfWebView.alpha = 1.0;
                             }
                             completion:nil];
            return;
        }
    }
    // UIWebPDFView doesnt exist yet so exit and try later
    [self startUIWebViewBackgroundFixTimer];
}

Now put this line right after the line where you load the pdf:

// if iOS 8 check if pdfWebView got subview of class UIWebPDFView
[self startUIWebViewBackgroundFixTimer];

Hints:

  • SYSTEM_VERSION_LESS_THAN is a macro to enshure the code is only execuded on iOS 8 and above.
  • self.pdfWebView is your UIWebView.


回答4:

The solution is here:

EDIT (link Broken)

How to remove black border from PDF/UIWebView in iOS 8

It worked for me.

The trick is to change the background color after webView finish loading.

EDIT

In iOS 8.0, if you are loading a PDF page in UIWebView then you will get the black border around the page.

When you load PDF in to webview then it insert UIWebPDFView into webview.

When PDF is rendered it add a new private class UIPDFPageView into UIWebPDFView which have a black background color. This all insertion happens after the -webViewDidFinishLoad: method called. So you need to set clear or white background color set after the -webViewDidFinishLoad: method

Add the following method for remove the black border color

Call the function in -webViewDidFinishLoad:

if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
    [self performSelector:@selector(clearBackground) withObject:nil afterDelay:0.1];
}

.

- (void)clearBackground {
    UIView *v = webVw;
    while (v) {
        //v.backgroundColor = [UIColor whiteColor];
        v = [v.subviews firstObject];

        if ([NSStringFromClass([v class]) isEqualToString:@"UIWebPDFView"]) {
            [v setBackgroundColor:[UIColor whiteColor]];

            // background set to white so fade view in and exit
            [UIView animateWithDuration:0.25 delay:0.0 options:UIViewAnimationOptionCurveEaseOut
                             animations:^{
                                 webVw.alpha = 1.0;
                             }
                             completion:nil];
            return;
        }
    }
}


回答5:

For anyone using Xamarin, a variation of gravers solution worked once translated to C#.

Place the following in the UIViewController:

public override void ViewDidLayoutSubviews()
    {
        base.ViewDidLayoutSubviews();

        var subViews = webView.Subviews;
        foreach (UIView v in subViews) {
            v.BackgroundColor = UIColor.Clear;

            var subv = v.Subviews;
            foreach (UIView w in subv) {
                w.BackgroundColor = UIColor.Clear;

            }
        }
    }


回答6:

I've made the answer in Swift. Feel free to use:

override func viewDidLayoutSubviews() {
    var view :UIView?
    view = PdfView as UIView
    while (view != nil) {
        view?.backgroundColor = UIColor.clearColor()
        view = view?.subviews.first as? UIView
    }
}

PDFView is the name of my UIWebView.



回答7:

This is railwayparade's implementation in Swift:

NSUserDefaults.standardUserDefaults().setObject(false, forKey: "WebKitDiskImageCacheEnabled")
NSUserDefaults.standardUserDefaults().synchronize()


回答8:

I have the same problem..Unable to fix it using the suggested solution i have also tried setting the back ground color in webViewDidFinishLoad delegate fucntion but of no use..

NSLog content :

-[TextualWebView webViewDidFinishLoad:](LNo.:141) PDF VIEW< UIWebView: 0x1c5ec6d0;  
frame = (36 41; 637 810); opaque = NO; autoresize = W+H; layer = < CALayer: 0x1c5ec740>>

-[TextualWebView webViewDidFinishLoad:](LNo.:141) PDF VIEW<_UIWebViewScrollView: 0x1c5f0070;  
 frame = (0 0; 637 810); clipsToBounds = YES; autoresize = H; gestureRecognizers = <NSArray: 0x1c5f02d0>;   
 layer = <CALayer: 0x1c5ef6f0>; contentOffset: {0, 0}; contentSize: {637, 810}>

-[TextualWebView webViewDidFinishLoad:](LNo.:141) PDF VIEW< UIWebPDFView: 0x1c6e6320;  
 frame = (0 0; 637 810); opaque = NO; gestureRecognizers = <NSArray: 0x1c6e0330>;  
 layer = < CALayer: 0x1c6e6410>>


回答9:

This is a mix of Heiko and gravers answers.

The view responsible for the black background is an instance of UIWebPDFView. So we can use gravers' approach, but without making every UIWebView subview white (which will mess the pages indicator).

Also, this solution makes no assumption on the UIWebPDFView position in the view hierarchy.

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    [self fixWebviewBackground:self.webView];
}

- (void)fixWebviewBackground:(UIView *)view
{
    if ([NSStringFromClass([view class]) isEqualToString:@"UIWebPDFView"]) {
        view.backgroundColor = nil;
        return;
    }
    for (UIView *subview in [view subviews]) {
        [self fixWebviewBackground:subview];
    }
}

We recursively descend on all subviews, changing only the UIWebPDFView's backgroundColor.



回答10:

I had this exact same issue, plus I was trying to zoom into my pdf after it had loaded. If this code was called prematurely it would render the pdf un-useable - you could not pan/scroll the pdf at all, you could still pinch to zoom but it only zoomed into the very top-left corner.

I initially had some code to remove the black border and zoom into the pdf after a given delay like so;

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    [self performSelector:@selector(zoomIntoPDF) withObject:nil afterDelay:0.6];
}

The 0.6s delay is useless though. It would (sometimes) still not be long enough when testing on an (in use) iPhone 4S. On the latest devices, a 0.6s hit in performance is ridiculous when it worked with a <0.05s delay.


@graver's solution helped me to investigate the issue. As suggested I was running NSLog(@"%@", v); in the while loop (I was also running NSLog(@"-------------"); before the while loop).

I was also calling viewDidLayoutSubviews after a delay of 0.6s like so;

[self performSelector:@selector(viewDidLayoutSubviews) withObject:nil afterDelay:0.6];

These are my logs:

-------------
<UIWebView: 0x7fd803c2e920; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <CALayer: 0x7fd803c18290>>
<_UIWebViewScrollView: 0x7fd803f6a720; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = H; gestureRecognizers = <NSArray: 0x7fd803f6afb0>; layer = <CALayer: 0x7fd803f6a660>; contentOffset: {0, 0}; contentSize: {320, 568}>
<UIWebBrowserView: 0x7fd80487b400; frame = (0 0; 320 568); opaque = NO; gestureRecognizers = <NSArray: 0x7fd803f65290>; layer = <UIWebLayer: 0x7fd803da2050>>
-------------
<UIWebView: 0x7fd803c2e920; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <CALayer: 0x7fd803c18290>>
<_UIWebViewScrollView: 0x7fd803f6a720; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = H; gestureRecognizers = <NSArray: 0x7fd803f6afb0>; layer = <CALayer: 0x7fd803f6a660>; contentOffset: {0, 0}; contentSize: {320, 568}>
<UIWebBrowserView: 0x7fd80487b400; frame = (0 0; 320 568); opaque = NO; gestureRecognizers = <NSArray: 0x7fd803f65290>; layer = <UIWebLayer: 0x7fd803da2050>>
Reachability Flag Status: -R -----l- networkStatusForFlags
Reachability Flag Status: -R ------- networkStatusForFlags
-------------
<UIWebView: 0x7fd803c2e920; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <CALayer: 0x7fd803c18290>>
<_UIWebViewScrollView: 0x7fd803f6a720; frame = (0 0; 320 568); clipsToBounds = YES; autoresize = H; gestureRecognizers = <NSArray: 0x7fd803f6afb0>; layer = <CALayer: 0x7fd803f6a660>; contentOffset: {0, 0}; contentSize: {320, 273.14641744548288}>
<UIWebPDFView: 0x7fd803c02570; frame = (0 0; 320 273.146); opaque = NO; gestureRecognizers = <NSArray: 0x7fd803c239d0>; layer = <CALayer: 0x7fd803c16a70>>
<UIPDFPageView: 0x7fd80601cf60; frame = (0 7; 320 259); tag = 1000000; gestureRecognizers = <NSArray: 0x7fd803c27db0>; layer = <CALayer: 0x7fd803ddd2f0>>

As you can see, UIPDFPageView does not appear until right at the end - the third and final time viewDidLayoutSubviews was called from the delay.


EDIT: This is a very similar principle to @Heiko's answer but uses a recursive method rather than a timer.

There must be a better solution, such as detecting a change in the webViews subviews, but this solution works for now:

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [self.webView setScalesPageToFit:YES];
    loopCap = 0;
    [self performSelector:@selector(hasPDFLoaded) withObject:nil afterDelay:0.05];
}

- (void)hasPDFLoaded
{
    BOOL containsPDF = NO;
    for (UIView *child in self.webView.scrollView.subviews)
    {
        if ([NSStringFromClass([child class]) isEqualToString:@"UIWebPDFView"]) {
            containsPDF = YES;
        }
    }
    if (containsPDF) {
        [self fixWebviewAndZoom];
    } else {
        if (loopCap < 20) {
            loopCap++;
            [self performSelector:@selector(hasPDFLoaded) withObject:nil afterDelay:0.05];
        }
    }
}

-(void)fixWebviewAndZoom {
    UIView *v = self.webView;
    while (v) {
        v.backgroundColor = [UIColor whiteColor];
        v = [v.subviews firstObject];
    }
    [self performSelector:@selector(zoomIntoPDF) withObject:nil afterDelay:0.05];
}

Please excuse the method and variable names, I will come up with more appropriate ones tomorrow!

As you can see, it is a recursive method which calls itself until the pdf has loaded. There is a cap to avoid any infinite loops (there should be an alert or something if it has not loaded in this time). The cap is 20 and the delay is 0.05s which allows 1 second for the pdf to load. I will probably increase the cap to 40, but I think 0.05 is ok performance wise, but I need to conduct some proper testing.

I hope this has been insightful, but I'd really appreciate some feedback or improvements to this solution.



回答11:

The simplest, but not so ideal fix is to add a border around the UIWebView of the same color as your pdf document's background color. This will prevent the ugliness to some extent until Apple fixes the issue.



回答12:

Changing the background color of the subviews of the UIWebView after the PDF finishes loading -- the essence of most of the answers above -- works for me. However, I've found that since iOS 5, webViewDidFinishLoad is called before the web view actually finishes loading, so I've been doing this to avoid various problems in my apps:

- (void)webViewDidFinishLoad:(UIWebView *)documentView {
    [self performSelector:@selector(webViewDidReallyFinishLoad:) withObject:documentView afterDelay:0.5];
}

- (void)webViewDidReallyFinishLoad:(UIWebView *)documentView {
    // now we can restore the transparency
}

The misleading behavior of webViewDidFinishLoad might explain why this approach isn't working for some people.

Another consequence of this behavior is that webView.scrollView.contentSize is not yet set when webViewDidFinishLoad is called. I submitted this to Apple as bug 10259756 in October 2011, but they closed the bug report with a status of "Behaves correctly."