preface

Since ios8 launched wKWebView, greatly improved web page loading speed and memory leakage problems, gradually replaced the cumbersome UIWebview. Although WKWebview with high performance and high refresh performs well in mixed development, it is still common to see abnormal white screen in the process of loading web pages, and the existing API protocol processing cannot capture such abnormal case, resulting in poor user useless waiting experience.

Based on the requirements of business scenarios, the system implements load white screen detection. Consider adopting the Webview optimization technical solution proposed by bytedance team. Take a screenshot of the current webView visual area at an appropriate loading time, and traverse the pixels of this snapshot. If the pixels of non-white screen color exceed a certain threshold, it will be considered as non-white screen, otherwise the reload request will be made.

The snapshots

Ios provides an easy webView snapshot interface to retrieve screen shots of the current viewable area through asynchronous callback.

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

The snapshotConfiguration parameter is used to configure the snapshot size range. By default, the entire screen area of the current client is captured. As the navigation bar may be loaded successfully but the content page is blank, the increase of non-white screen pixel points will affect the final judgment result, so it is considered to be removed.

- (void)judgeLoadingStatus:(WKWebView *)webview {if (@available(iOS 11.0, *)) { if (webView && [webView isKindOfClass:[WKWebView class]]) { CGFloat statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height; . / / the status bar height CGFloat navigationHeight = webView viewController. NavigationController. NavigationBar. Frame. The size, height; WKSnapshotConfiguration *shotConfiguration = [[WKSnapshotConfiguration alloc] init]; shotConfiguration.rect = CGRectMake(0, statusBarHeight + navigationHeight, _webView.bounds.size.width, (_webView.bounds.size.height - navigationHeight - statusBarHeight)); / / only capture detection of the navigation bar below part [_webView takeSnapshotWithConfiguration: shotConfiguration completionHandler: ^ (UIImage * _Nullable snapshotImage, NSError * _Nullable error) { //todo }]; }}}Copy the code

Zoom snapshot

To improve detection performance, consider scaling the snapshot to 1/5 to reduce the total number of pixels and speed up 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 (@ the 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; }}Copy the code

Performance comparison before and after narrowing (experimental environment: under the same page of iPhone11) :

White screen detection before zooming:

20 ms

White screen detection after zooming:

Took 13 ms

Notice there’s a little crater here. Since the thumbnail size may not be an integer after the width/height * scaling factor, the canvas is rounded up by default when repainting, which causes the canvas to be larger than the actual thumbnail. . When traversing thumbnail pixels, the pixels on the canvas outside the image will be taken into account, resulting in the pixel ratio of the actual white screen page is not 100% as shown in the figure. So use floor to round down its size.

Traverse the snapshot

Iterate through the thumbnail pixels of the snapshot, and consider the pages with white pixels (R:255 G: 255 B: 255) accounting for more than 95% of the pixels as 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 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(@" totalCount :%d, %d, %f",totalCount, whiteCount, proportion); Proportion > 0.95 {return YES; }else{ return NO; }}Copy the code

conclusion

Typedef NS_ENUM (NSUInteger webviewLoadingStatus) {WebViewNormalStatus = 0, / / normal WebViewErrorStatus, // WebViewPendStatus, // pending}; JudgeLoadingStatus :(WKWebview *)webview withBlock:(void (^)(webviewLoadingStatus status))completionBlock{ webviewLoadingStatus __block status = WebViewPendStatus; If (@ the available (iOS 11.0, *)) { if (webview && [webview isKindOfClass:[WKWebView class]]) { CGFloat statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height; . / / the status bar height CGFloat navigationHeight = webview viewController. NavigationController. NavigationBar. Frame. The size, height; WKSnapshotConfiguration *shotConfiguration = [[WKSnapshotConfiguration alloc] init]; shotConfiguration.rect = CGRectMake(0, statusBarHeight + navigationHeight, webview.bounds.size.width, (webview.bounds.size.height - navigationHeight - statusBarHeight)); / / only capture detection of the navigation bar below part [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); } }]; SearchEveryPixel :(UIImage *)image {CGImageRef cgImage = [image cgImage]; searchEveryPixel:(UIImage *)image {cgImage cgImage = [image cgImage]; size_t width = CGImageGetWidth(cgImage); size_t height = CGImageGetHeight(cgImage); size_t bytesPerRow = CGImageGetBytesPerRow(cgImage); // Each pixel contains 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(@" totalCount :%d, %d, %f",totalCount, whiteCount, proportion); Proportion > 0.95 {return YES; }else{ return NO; }} // scale 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 (@ the 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; }}Copy the code

Simply use this function method during the appropriate view life cycle to detect whether the page state is blank, with negligible performance loss.

The statement

This article is the author’s original, please indicate the source of reprint.