iOS WKWebview white screen detection implementation

foreword

Since the launch of wkwebview in ios8, it has greatly improved the web page loading speed and memory leak problems, and gradually replaced the cumbersome UIWebview. Although the high-performance, high-refresh WKWebview excels in mixed development, it is still not uncommon for abnormal white screens to appear in the process of loading web pages, and the existing api protocol processing cannot capture such abnormal case s, causing users Useless waiting experience is bad.

For business scenario requirements, realize loading white screen detection. Consider adopting the webview optimization technical solution proposed by the ByteDance team. Take a screenshot of the visible area of ​​​​the current webview at the appropriate loading time, and perform pixel point traversal on this snapshot. If the pixels of the non-white screen color exceed a certain threshold, it is determined to be a non-white screen, and vice versa, reload the request.

take a snapshot

ios officially provides a simple interface for obtaining webview snapshots, and obtains screenshots of the current visible area through asynchronous callbacks.

- (void)takeSnapshotWithConfiguration:(nullable WKSnapshotConfiguration *)snapshotConfiguration completionHandler:(void (^)(UIImage * _Nullable snapshotImage, NSError * _Nullable error))completionHandler API_AVAILABLE(ios(11.0));

The snapshotConfiguration parameter can be used to configure the size range of the snapshot. By default, the entire screen area of ​​the current client is captured. Due to the special situation that the navigation bar is successfully loaded but the content page is blank, the increase in the number of non-white screen pixels will affect the final judgment result, so consider removing it.

- (void)judgeLoadingStatus:(WKWebView *)webview {
    if (@available(iOS 11.0, *)) {
        if (webView && [webView isKindOfClass:[WKWebView class]]) {

            CGFloat statusBarHeight =  [[UIApplication sharedApplication] statusBarFrame].size.height; //Status bar height
            CGFloat navigationHeight =  webView.viewController.navigationController.navigationBar.frame.size.height; //navigation bar height
            WKSnapshotConfiguration *shotConfiguration = [[WKSnapshotConfiguration alloc] init];
            shotConfiguration.rect = CGRectMake(0, statusBarHeight + navigationHeight, _webView.bounds.size.width, (_webView.bounds.size.height - navigationHeight - statusBarHeight)); //Only screenshots detect the following part of the navigation bar
            [_webView takeSnapshotWithConfiguration:shotConfiguration completionHandler:^(UIImage * _Nullable snapshotImage, NSError * _Nullable error) {
                //todo
            }];
        }
    }
}

zoom snapshot

To improve detection performance, consider scaling the snapshot to 1/5, reducing the total number of pixels, thus speeding up the traversal.

- (UIImage *)scaleImage: (UIImage *)image {
    CGFloat scale = 0.2;
    CGSize newsize;
    newsize.width = floor(image.size.width * scale);
    newsize.height = floor(image.size.height * scale);
    if (@available(iOS 10.0, *)) {
        UIGraphicsImageRenderer * renderer = [[UIGraphicsImageRenderer alloc] initWithSize:newsize];
          return [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
                        [image drawInRect:CGRectMake(0, 0, newsize.width, newsize.height)];
                 }];
    }else{
        return image;
    }
}  

Performance comparison before and after scaling down (experimental environment: under the same page of iPhone 11):

White screen detection before scaling:

Takes 20ms

White screen detection after scaling:

Takes 13ms

As a developer, it is very important to have a learning atmosphere and a communication circle. There is an iOS communication group: 642363427 , whether you are a novice or a Daniel, welcome to join, share BAT, Ali interview questions, interview experience, and discuss technology!

Note that there is a small hole here. Since the size of the thumbnail may not be an integer after the original image width and height * zoom factor, it is rounded up by default when the canvas is redrawn, which causes the canvas to be larger than the actual thumbnail (bastard!). When traversing the thumbnail pixels, the pixels on the canvas outside the picture will be taken into account, resulting in that the actual white screen page pixel ratio is not 100% as shown in the figure. So use floor to round down its size.

Traverse snapshots

Traverse the pixels of the snapshot thumbnails, and identify pages with white pixels (R:255 G: 255 B: 255) that account for more than 95% of the screen as white.

- (BOOL)searchEveryPixel:(UIImage *)image {
    CGImageRef cgImage = [image CGImage];
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);
    size_t bytesPerRow = CGImageGetBytesPerRow(cgImage); //Each pixel contains r g b a four bytes
    size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);

    CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage);
    CFDataRef data = CGDataProviderCopyData(dataProvider);
    UInt8 * buffer = (UInt8*)CFDataGetBytePtr(data);

    int whiteCount = 0;
    int totalCount = 0;

    for (int j = 0; j < height; j ++ ) {
        for (int i = 0; i < width; i ++) {
            UInt8 * pt = buffer + j * bytesPerRow + i * (bitsPerPixel / 8);
            UInt8 red   = * pt;
            UInt8 green = *(pt + 1);
            UInt8 blue  = *(pt + 2);
//            UInt8 alpha = *(pt + 3);

            totalCount ++;
            if (red == 255 && green == 255 && blue == 255) {
                whiteCount ++;
            }
        }
    }
    float proportion = (float)whiteCount / totalCount ;
    NSLog(@"Current pixel count:%d,white pixel count:%d , proportion: %f",totalCount , whiteCount , proportion );
    if (proportion > 0.95) {
        return YES;
    }else{
        return NO;
    }
}  

Summarize

typedef NS_ENUM(NSUInteger,webviewLoadingStatus) {

    WebViewNormalStatus = 0, //normal

    WebViewErrorStatus, //white screen

    WebViewPendStatus, //pending
};

// Determine whether the screen is white
- (void)judgeLoadingStatus:(WKWebview *)webview  withBlock:(void (^)(webviewLoadingStatus status))completionBlock{
    webviewLoadingStatus __block status = WebViewPendStatus;
    if (@available(iOS 11.0, *)) {
        if (webview && [webview isKindOfClass:[WKWebView class]]) {

            CGFloat statusBarHeight =  [[UIApplication sharedApplication] statusBarFrame].size.height; //Status bar height
            CGFloat navigationHeight = webview.viewController.navigationController.navigationBar.frame.size.height; //navigation bar height
            WKSnapshotConfiguration *shotConfiguration = [[WKSnapshotConfiguration alloc] init];
            shotConfiguration.rect = CGRectMake(0, statusBarHeight + navigationHeight, webview.bounds.size.width, (webview.bounds.size.height - navigationHeight - statusBarHeight)); //Only screenshots detect the following part of the navigation bar
            [webview takeSnapshotWithConfiguration:shotConfiguration completionHandler:^(UIImage * _Nullable snapshotImage, NSError * _Nullable error) {
                if (snapshotImage) {
                    CGImageRef imageRef = snapshotImage.CGImage;
                    UIImage * scaleImage = [self scaleImage:snapshotImage];
                    BOOL isWhiteScreen = [self searchEveryPixel:scaleImage];
                    if (isWhiteScreen) {
                       status = WebViewErrorStatus;
                    }else{
                       status = WebViewNormalStatus;
                    }
                }
                if (completionBlock) {
                    completionBlock(status);
                }
            }];
        }
    }
}

// Traversing the pixels, the proportion of white pixels is greater than 95%, and it is considered as a white screen
- (BOOL)searchEveryPixel:(UIImage *)image {
    CGImageRef cgImage = [image CGImage];
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);
    size_t bytesPerRow = CGImageGetBytesPerRow(cgImage); //Each pixel contains r g b a four bytes
    size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);

    CGDataProviderRef dataProvider = CGImageGetDataProvider(cgImage);
    CFDataRef data = CGDataProviderCopyData(dataProvider);
    UInt8 * buffer = (UInt8*)CFDataGetBytePtr(data);

    int whiteCount = 0;
    int totalCount = 0;

    for (int j = 0; j < height; j ++ ) {
        for (int i = 0; i < width; i ++) {
            UInt8 * pt = buffer + j * bytesPerRow + i * (bitsPerPixel / 8);
            UInt8 red   = * pt;
            UInt8 green = *(pt + 1);
            UInt8 blue  = *(pt + 2);
//            UInt8 alpha = *(pt + 3);

            totalCount ++;
            if (red == 255 && green == 255 && blue == 255) {
                whiteCount ++;
            }
        }
    }
    float proportion = (float)whiteCount / totalCount ;
    NSLog(@"Current pixel count:%d,white pixel count:%d , proportion: %f",totalCount , whiteCount , proportion );
    if (proportion > 0.95) {
        return YES;
    }else{
        return NO;
    }
}

//zoom image
- (UIImage *)scaleImage: (UIImage *)image {
    CGFloat scale = 0.2;
    CGSize newsize;
    newsize.width = floor(image.size.width * scale);
    newsize.height = floor(image.size.height * scale);
    if (@available(iOS 10.0, *)) {
        UIGraphicsImageRenderer * renderer = [[UIGraphicsImageRenderer alloc] initWithSize:newsize];
          return [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
                        [image drawInRect:CGRectMake(0, 0, newsize.width, newsize.height)];
                 }];
    }else{
        return image;
    }
}  

It is only necessary to call back and use this function method in the appropriate view life cycle to detect whether the page status is white, and the performance loss is negligible.

 

Posted by cowboysdude on Tue, 10 May 2022 15:59:52 +0300