Use of iOS WKWebView

Posted by snorky on Wed, 05 Jan 2022 11:24:40 +0100

preface

Recently, the UIWebView in the project was replaced with WKWebView, so let's summarize.
Example Demo: Use of WKWebView
This article will introduce WKWebView from the following aspects:
1. Some classes involved in WKWebView
2. Proxy methods involved in WKWebView
3. Implementation of web content loading progress bar and title
4. Interaction between JS and OC
5. Implementation of local HTML file
6. WKWebView+UITableView mixed row
7. WKWebView offline cache function

1, Some classes involved in WKWebView

WKWebView: rendering and presentation of web pages

be careful: #import <WebKit/WebKit.h>

//initialization
_webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) configuration:config];
// UI agent
_webView.UIDelegate = self;
// Navigation agent
_webView.navigationDelegate = self;
// Whether to allow the gesture to slide left to return to the previous level, similar to the left slide return of navigation control
_webView.allowsBackForwardNavigationGestures = YES;
//A list of pages that can be returned to store web pages that have been opened 
WKBackForwardList * backForwardList = [_webView backForwardList];

//        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.chinadaily.com.cn"]];
//        [request addValue:[self readCurrentCookieWithDomain:@"http://www.chinadaily.com.cn"] forHTTPHeaderField:@"Cookie"];
//        [_webView loadRequest:request];
//Page back
[_webView goBack];
//Page forward
 [_webView goForward];
//Refresh current page
[_webView reload];
    
NSString *path = [[NSBundle mainBundle] pathForResource:@"JStoOC.html" ofType:nil];
NSString *htmlString = [[NSString alloc]initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
//Load local html file
[_webView loadHTMLString:htmlString baseURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]];

WKWebView configuration: adds WKWebView configuration information for

//Create web page configuration object
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
        
// Create settings object
WKPreferences *preference = [[WKPreferences alloc]init];
//Minimum font size when the JavaScript enabled property is set to NO, you can see a significant effect
preference.minimumFontSize = 0;
//Sets whether javaScript is supported. It is supported by default
preference.javaScriptEnabled = YES;
// On iOS, the default value is NO, which indicates whether javaScript can automatically open windows without user interaction
preference.javaScriptCanOpenWindowsAutomatically = YES;
config.preferences = preference;

// Whether to play online with h5 video player or full screen with native player
config.allowsInlineMediaPlayback = YES;
//Set whether the video needs to be played manually by the user. If set to NO, automatic playback will be allowed
config.requiresUserActionForMediaPlayback = YES;
//Set whether to allow picture in picture technology to work on a specific device
config.allowsPictureInPictureMediaPlayback = YES;
//Available after setting the application name iOS9 in the requested user agent information
config.applicationNameForUserAgent = @"ChinaDailyForiPad";
 //The custom WKScriptMessageHandler is used to solve the problem that memory is not released
WeakWebViewScriptMessageDelegate *weakScriptMessageDelegate = [[WeakWebViewScriptMessageDelegate alloc] initWithDelegate:self];
//This class is mainly used to manage the interaction between native and JavaScript
WKUserContentController * wkUController = [[WKUserContentController alloc] init];
//Register a js method with the name jsToOcNoPrams
[wkUController addScriptMessageHandler:weakScriptMessageDelegate  name:@"jsToOcNoPrams"];
[wkUController addScriptMessageHandler:weakScriptMessageDelegate  name:@"jsToOcWithPrams"]; 
config.userContentController = wkUController;

WKUserScript: used for JavaScript injection

// The following code adapts to the text size. After changing from UIWebView to WKWebView, you will find that the font is much smaller. This should be the compatibility between WKWebView and html. The solution is to modify the original web page, or we can manually inject JS
NSString *jSString = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
// For JavaScript injection
WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jSString injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
[config.userContentController addUserScript:wkUScript];

WKUserContentController: this class is mainly used to manage the interaction between native and JavaScript

//This class is mainly used to manage the interaction between native and JavaScript
WKUserContentController * wkUController = [[WKUserContentController alloc] init];
//Register a JS method with the name jsToOcNoPrams, and set the agent that handles receiving JS methods
[wkUController addScriptMessageHandler:self  name:@"jsToOcNoPrams"];
[wkUController addScriptMessageHandler:self  name:@"jsToOcWithPrams"];
config.userContentController = wkUController;
//Remember to remove after use
//Remove registered js methods
[[_webView configuration].userContentController removeScriptMessageHandlerForName:@"jsToOcNoPrams"];
[[_webView configuration].userContentController removeScriptMessageHandlerForName:@"jsToOcWithPrams"];

WKScriptMessageHandler: this protocol class is specially used to handle listening JavaScript methods to call native OC methods. It is used with WKUserContentController.

Note: the WKScriptMessageHandler protocol is followed, and the proxy is set by WKUserContentControl

//Callback method for capturing by name of receiving JS outgoing message
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    NSLog(@"name:%@\\\\n body:%@\\\\n frameInfo:%@\\\\n",message.name,message.body,message.frameInfo);
    //Use message Body get the parameter body from JS
    NSDictionary * parameter = message.body;
    //JS calls OC
    if([message.name isEqualToString:@"jsToOcNoPrams"]){
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"js Call to oc" message:@"Without parameters" preferredStyle:UIAlertControllerStyleAlert];
        [alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        }])];
        [self presentViewController:alertController animated:YES completion:nil];
        
    }else if([message.name isEqualToString:@"jsToOcWithPrams"]){
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"js Call to oc" message:parameter[@"params"] preferredStyle:UIAlertControllerStyleAlert];
        [alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        }])];
        [self presentViewController:alertController animated:YES completion:nil];
    }
}

2, Proxy methods involved in WKWebView

WKNavigationDelegate: mainly handles some jump and load operations

// Called when the page starts loading
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
}

// Called when the page fails to load
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
    [self.progressView setProgress:0.0f animated:NO];
} 

// Called when the content begins to return
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
}

// After the page is loaded, it is called.
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    [self getCookie];
}

//Called when a commit error occurs
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
    [self.progressView setProgress:0.0f animated:NO];
}  

// After the server jump request is redirected, it is called later.
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
}

// Whether to jump is determined according to WebView's HTTP request header information and related information
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    NSString * urlStr = navigationAction.request.URL.absoluteString;
    NSLog(@"Send jump request:%@",urlStr);
    //Self defined protocol header
    NSString *htmlHeadString = @"github://";
    if([urlStr hasPrefix:htmlHeadString]){
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"By interception URL call OC" message:@"You want to go to my Github homepage?" preferredStyle:UIAlertControllerStyleAlert];
        [alertController addAction:([UIAlertAction actionWithTitle:@"cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {   
        }])];
        [alertController addAction:([UIAlertAction actionWithTitle:@"open" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            NSURL * url = [NSURL URLWithString:[urlStr stringByReplacingOccurrencesOfString:@"github://callName_?" withString:@""]];
            [[UIApplication sharedApplication] openURL:url];
        }])];
        [self presentViewController:alertController animated:YES completion:nil];
        decisionHandler(WKNavigationActionPolicyCancel);
    }else{
        decisionHandler(WKNavigationActionPolicyAllow);
    }
}
    
// Whether the client can jump is determined according to the server response header and response related information received by the client
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
    NSString * urlStr = navigationResponse.response.URL.absoluteString;
    NSLog(@"Current jump address:%@",urlStr);
    //Allow jump
    decisionHandler(WKNavigationResponsePolicyAllow);
    //Jump not allowed
    //decisionHandler(WKNavigationResponsePolicyCancel);
} 

//It is called when the response authentication is required. Similarly, the user identity certificate needs to be passed in the block
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler{
    //User identity information
    NSURLCredential * newCred = [[NSURLCredential alloc] initWithUser:@"user123" password:@"123" persistence:NSURLCredentialPersistenceNone];
    //Provide a credential for the sender of the challenge
    [challenge.sender useCredential:newCred forAuthenticationChallenge:challenge];
    completionHandler(NSURLSessionAuthChallengeUseCredential,newCred);
}
//Called when the process is terminated
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView{
}

WKUIDelegate: mainly deals with JS scripts, confirmation boxes, warning boxes, etc

/**
  *  web Called when a warning box pops up in the interface
  *
  *  @param webView           Implement the webview of the agent
  *  @param message           Content in warning box
  *  @param completionHandler Warning box disappear call
  */
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"HTML Pop up box for" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
}

// Confirmation box
//After JavaScript calls the confirm method, the callback method confirm is the confirmation box in js, and the user's selection needs to be passed in the block
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }])];
    [alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
}

// Input box
//The callback method after JavaScript calls the prompt method. Prompt is the input box in js, and the information entered by the user needs to be passed in the block
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.text = defaultText;
    }];
    [alertController addAction:([UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(alertController.textFields[0].text?:@"");
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
}

// The page is a pop-up window_ blank processing
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
    if (!navigationAction.targetFrame.isMainFrame) {
        [webView loadRequest:navigationAction.request];
    }
    return nil;
}

3, Implementation of web content loading progress bar and title

//Add an observer to monitor the progress of web page loading
[self.webView addObserver:self
                   forKeyPath:@"estimatedProgress"
                   options:0
                   context:nil];
//Add an observer to monitor the title of the page title
[self.webView addObserver:self
                   forKeyPath:@"title"
                   options:NSKeyValueObservingOptionNew
                   context:nil];

//kvo monitoring progress must implement this method
-(void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                     change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                     context:(void *)context{
    if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))]
        && object == _webView) {
       NSLog(@"Page loading progress = %f",_webView.estimatedProgress);
        self.progressView.progress = _webView.estimatedProgress;
        if (_webView.estimatedProgress >= 1.0f) {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                self.progressView.progress = 0;
            });
        } 
    }else if([keyPath isEqualToString:@"title"]
             && object == _webView){
        self.navigationItem.title = _webView.title;
    }else{
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}

//Remove observer
[_webView removeObserver:self forKeyPath:NSStringFromSelector(@selector(estimatedProgress))];
[_webView removeObserver:self forKeyPath:NSStringFromSelector(@selector(title))];

4, Interaction between JS and OC

JS calls OC

This implementation mainly relies on two classes: WKScriptMessageHandler protocol class and WKUserContentController: the WKUserContentController object is responsible for registering JS methods and setting the agent for processing and receiving JS methods. The agent complies with WKScriptMessageHandler and implements the callback method for capturing JS messages. For details, see the introduction of these two classes in step 1.

//This class is mainly used to manage the interaction between native and JavaScript
WKUserContentController * wkUController = [[WKUserContentController alloc] init];
//Register a JS method with the name jsToOcNoPrams, and set the agent that handles receiving JS methods
[wkUController addScriptMessageHandler:self  name:@"jsToOcNoPrams"];
[wkUController addScriptMessageHandler:self  name:@"jsToOcWithPrams"];
config.userContentController = wkUController;

Note: Observe WKScriptMessageHandler Agreement, the agent is WKUserContentControl set up
 //The callback method of capturing through the name of the js outgoing message is called OC
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    NSLog(@"name:%@\\\\n body:%@\\\\n frameInfo:%@\\\\n",message.name,message.body,message.frameInfo);
}

OC calls JS

 //OC calls JS changecolor(), which is the JS method name, and completionHandler is the asynchronous callback block
NSString *jsString = [NSString stringWithFormat:@"changeColor('%@')", @"Js parameter"];
[_webView evaluateJavaScript:jsString completionHandler:^(id _Nullable data, NSError * _Nullable error) {
    NSLog(@"change HTML Background color of");
}];
    
//Change the font size and call the native JS method
NSString *jsFont = [NSString stringWithFormat:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '%d%%'", arc4random()%99 + 100];
[_webView evaluateJavaScript:jsFont completionHandler:nil];

5, Implementation of local HTML file

because Sample Demo My needs and knowledge are limited. I wrote an HTML file with only a little skin of HTML, CSS and JavaScript. It's amateur. Don't spray it 😁😁
If Xiaobai wants to learn this knowledge, you can see here: http://www.w3school.com.cn/index.html

I use MAC's own text editing tool to generate a file, change the suffix, and force it to At the same time, you also need to set text editing to display the code when opening the HTML file (as shown in the figure below), and then edit the code.

6. WKWebView+UITableView shuffling and 7. WKWebView offline caching function iOS_Tips-12 Check here.

For more information, please go to my Github: Use of WKWebView

Topics: iOS