0%

iOS 编程:WKWebView

WKWebView API

WKWebView 对象可以显示交互式 Web 内容,例如应用内浏览器。你可以使用 WKWebView 类将 Web 内容嵌入到你的应用程序中。 为此,创建一个 WKWebView 对象,像设置视图一样设置它,并向其发送加载 Web 内容的请求。

预览

重要

从 iOS 8.0 和 OS X 10.10 开始,在你的 APP 中使用 WKWebView 添加网页内容,不要使用 UIWebViewWebView

使用 initWithFrame:configuration: 方法创建了一个新的 WKWebView 对象之后,你需要加载 web 内容。使用 loadHTMLString:baseURL: 方法加载本地 HTML 文件或者使用 loadRequest: 方法开始加载 web 内容。使用 stopLoading 方法停止加载,并且使用 loading 属性查看 web 视图是否正在加载中。为对象设置委托属性以遵守 WKUIDelegate 协议,以跟踪 Web 内容的加载。

要允许用户在 web 历史页面中前进或者后退,为按钮设置使用 goBackgoForward 方法的动作。当用户不能在某个方向上再移动时,使用 canGoBackcanGoForward 属性禁用按钮。

默认情况下,web 视图会自动将出现在 web 内容中的电话号码转换为电话链接。当电话链接被点击时,电话应用程序就会启动并拨打该号码。要关闭这个默认的行为,用 WKDataDetectorTypes 设置 dataDetectorTypes 属性以不包含 WKDataDetectorTypePhoneNumber 标志。

你还可以使用 setMagnification:centeredAtPoint: 以编程方式设置 Web 内容第一次在 Web 视图中显示的缩放比例。 此后,用户可以使用手势来改变比例。

Symbols

初始化 web 视图

  • configuration

    用于初始化 Web 视图的配置副本。

  • - initWithFrame:configuration:

    用指定的 frame 和 configuration 初始化 web 视图 。

  • - initWithCoder:


查看 web 信息

  • scrollView

    与 web 视图相关联的滚动视图。

  • title

    页面标题。

  • URL

    活动网址。

  • customUserAgent

    自定义用户代理字符串。

  • serverTrust

    用于当前待导航的 SecTrustRef 对象。


设置委托

  • navigationDelegate

    web 视图的导航代理。

  • UIDelegate

    web 视图的用户界面委托。


加载内容

  • estimatedProgress

    当前导航的哪几个部分已经加载的估计值(double:0.0~1.0)。

  • hasOnlySecureContent

    布尔值,指示页面上的所有资源是否通过安全加密的连接加载。

  • - loadHTMLString:baseURL:

    设置网页内容和 base URL。

  • loading

    布尔值,显示当前视图是否正在加载。

  • - reload

    重新加载当前页面

  • - reloadFromOrigin

    重新加载当前页面,如果可能,使用缓存验证条件执行端到端重新验证。

  • - stopLoading

    停止加载当前页面所有资源。

  • - loadData:MIMEType:characterEncodingName:baseURL:

    设置网页内容和 base URL。

  • - loadFileURL:allowingReadAccessToURL:

    找到文件系统上所请求的文件 URL


缩放内容

  • allowsMagnification

    布尔值,表示放大手势是否会改变网页视图的放大倍数。

  • magnification

    页面内容当前缩放因子。

  • - setMagnification:centeredAtPoint:

    按指定的因子缩放页面内容,并将结果居中在指定的点上。


导航

  • allowsBackForwardNavigationGestures

    布尔值,指示水平滑动手势是否会触发后退列表导航。默认为:NO。

  • backForwardList

    网页视图的后退列表。(之前访问过的 web 页面的列表)

  • canGoBack

    布尔值,指示后退列表中是否有可被导航到的后退项。

  • canGoForward

    布尔值,指示后退列表中是否有可导航的前进项。

  • allowsLinkPreview

    布尔值,用于确定是否按下链接可以显示链接目标的预览。

  • - goBack

    导航到后退列表中的后退项中。

  • - goForward

    导航到后退列表中的前进项中。

  • - goToBackForwardListItem:

    导航到后退列表中的某一个网页项,并将其设置为当前项。

  • - loadRequest:

    导航到请求的 URL 地址


执行 JavaScript

  • - evaluateJavaScript:completionHandler:

    评估 JavaScript 字符串。Objective-C 调用 JavaScript 方法


实例方法

  • - goBack

    导航到后退列表中的后退项中。

  • - goForward

    导航到后退列表中的前进项中。

  • - reload

    重新加载当前页面

  • - reloadFromOrigin

    重新加载当前页面,如果可能,使用缓存验证条件执行端到端重新验证。

  • - stopLoading

    停止加载当前页面所有资源。


WKWebViewConfiguration API

用于初始化 Web 视图的属性集合。

预览

使用 WKWebViewConfiguration 类,你可以确定网页呈现的速度,媒体播放的处理方式,用户可以选择的项目的粒度等等。

WKWebViewConfiguration 仅在首次初始化 Web 视图时使用。 当 web 视图被创建以后,你就无法再使用此类来更改 Web 视图的配置信息了。

Symbols

配置新的 Web 视图的属性

  • applicationNameForUserAgent

    在用户代理字符串中使用的应用程序的名称。

  • preferences

    Web 视图要使用的首选项对象。

  • processPool

    从中获取视图的 Web 内容进程的进程池。

  • userContentController

    与网页视图关联的用户内容控制器。

  • websiteDataStore

    由网页视图使用的存储的网站数据。


确定页面可扩展性

  • ignoresViewportScaleLimits

    布尔值,用于确定 WKWebView 对象是否应始终允许缩放网页。


设置渲染首选项

  • suppressesIncrementalRendering

    布尔值,指示网络视图是否在【内容渲染完全加载到内存之前】禁止内容呈现。默认值:NO。


设置媒体播放首选项

  • allowsInlineMediaPlayback

    布尔值,指示 HTML5 视频是否内嵌播放或使用 native 全屏控制器。

  • allowsAirPlayForMediaPlayback

    是否允许 AirPlay

  • allowsPictureInPictureMediaPlayback

    HTML5 视频是否可以播放画中画。

  • mediaTypesRequiringUserActionForPlayback

    确定哪些媒体类型需要用户手势才能开始播放。

  • WKAudiovisualMediaTypes

    枚举类型:需要用户手势开始播放的媒体类型

    typedef enum WKAudiovisualMediaTypes : NSUInteger {
      WKAudiovisualMediaTypeNone = 0,
      WKAudiovisualMediaTypeAudio = 1 << 0,
      WKAudiovisualMediaTypeVideo = 1 << 1,
      WKAudiovisualMediaTypeAll = NSUIntegerMax
    } WKAudiovisualMediaTypes;

设置选择粒度

  • selectionGranularity

    用户可以在网页视图中交互地选择内容的粒度级别。

  • WKSelectionGranularity

    枚举类型:交互式创建和修改选择的粒度。

    typedef enum WKSelectionGranularity : NSInteger {
      WKSelectionGranularityDynamic,    //选择粒度根据选择而自动变化。
      WKSelectionGranularityCharacter //选择端点可以放置在任何字符边界上
    } WKSelectionGranularity;

选择用户界面方向

  • userInterfaceDirectionPolicy

    用户界面元素的方向。

  • WKUserInterfaceDirectionPolicy

    枚举类型:用于确定 Web 视图中用户界面元素的方向性策略。

    typedef enum WKUserInterfaceDirectionPolicy : NSInteger {
      WKUserInterfaceDirectionPolicyContent,    // 方向遵循CSS / HTML / XHTML规范
      WKUserInterfaceDirectionPolicySystem // 方向遵循视图的userInterfaceLayoutDirection属性
    } WKUserInterfaceDirectionPolicy;

识别数据类型

  • dataDetectorTypes

    所需的数据检测类型。

  • WKDataDetectorTypes

    枚举类型:检测到的数据类型。

    typedef enum WKDataDetectorTypes : NSUInteger {
      WKDataDetectorTypeNone = 0,
      WKDataDetectorTypePhoneNumber = 1 << 0,
      WKDataDetectorTypeLink = 1 << 1,
      WKDataDetectorTypeAddress = 1 << 2,
      WKDataDetectorTypeCalendarEvent = 1 << 3,
      WKDataDetectorTypeTrackingNumber = 1 << 4,
      WKDataDetectorTypeFlightNumber = 1 << 5,
      WKDataDetectorTypeLookupSuggestion = 1 << 6,
      WKDataDetectorTypeAll = NSUIntegerMax,
      WKDataDetectorTypeSpotlightSuggestion = WKDataDetectorTypeLookupSuggestion
    } WKDataDetectorTypes;

WKNavigationDelegate API

WKNavigationDelegate 协议方法可以帮助你实现在 Web 视图接受,加载和完成导航请求的过程中触发的自定义行为。

初始化导航和跟踪加载进度

//  页面开始加载web内容时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation;

//  当web内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation;

//  页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation;

//  页面加载失败时调用 ( 【web视图加载内容时】发生错误)
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error;

// 【web视图导航过程中发生错误】时调用。
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error;

// 当Web视图的Web内容进程终止时调用。
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView;
  • 导航错误:NSURLErrorDomain Code=-999
    原因:webview 的上一个请求还没有加载完成,下一个请求发起了,此时 webview 会取消掉之前的请求,因此会回调导航失败错误(NSURLErrorCancelled = -999)。

响应服务器操作

// 收到服务器重定向之后调用 (接收到服务器跳转请求)
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation;

实例方法

  • - webView:didReceiveAuthenticationChallenge:completionHandler:

当 Web 视图需要响应认证挑战 (authentication challenge) 时调用。

当使用 Https 协议加载 web 内容时,使用的证书不合法或者证书过期时需要使用该方法:

- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        if ([challenge previousFailureCount] == 0) {
            NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
        } else {
            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
        }
    } else {
        completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}

允许导航

// 通常用于处理跨域的链接能否导航

// 在发送请求之前,决定是否允许或取消导航。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    NSLog(@"%s", __FUNCTION__);
    // 如果请求的是百度地址,则允许跳转
    if ([navigationAction.request.URL.host.lowercaseString isEqual:@"www.baidu.com"]) {
        // 允许跳转
        decisionHandler(WKNavigationActionPolicyAllow);
        return;
    }
    // 否则不允许跳转
    decisionHandler(WKNavigationActionPolicyCancel);
}

// 收到响应后,决定是否允许或取消导航。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
    NSLog(@"%s", __FUNCTION__);
    // 如果响应的地址是百度,则允许跳转
    if ([navigationResponse.response.URL.host.lowercaseString isEqual:@"www.baidu.com"]) {
        // 允许跳转
        decisionHandler(WKNavigationResponsePolicyAllow);
        return;
    }
    // 否则不允许跳转
    decisionHandler(WKNavigationResponsePolicyCancel);
}

导航策略

  • WKNavigationActionPolicy

    decisionPolicyForNavigationAction:decisionHandler:方法返回的决定策略。

    typedef enum WKNavigationActionPolicy : NSInteger {
      WKNavigationActionPolicyCancel,
      WKNavigationActionPolicyAllow
    } WKNavigationActionPolicy;
  • WKNavigationResponsePolicy

    webView:decidePolicyForNavigationResponse:decisionHandler: 方法返回的决定策略。

    typedef enum WKNavigationResponsePolicy : NSInteger {
      WKNavigationResponsePolicyCancel,
      WKNavigationResponsePolicyAllow
    } WKNavigationResponsePolicy;

WKUIDelegate API

WKUIDelegate 类是网页视图的用户界面委托协议,提供了代表网页呈现本机用户界面元素的方法。

Web 视图用户界面委托对象实现此协议来控制打开的新窗口,增加用户单击元素时显示的默认菜单项的行为,并执行其他与用户界面相关的任务。 可以通过处理 JavaScript 或其他插件内容来调用这些方法。 默认 Web 视图实现假定每个 Web 视图有一个窗口,因此非常规用户界面可能会实现用户界面委托。

参考:【iOS 开发】WKWebView 学习笔记 (3)-WKUIDelegate

Symbols

创建 web 视图

  • - webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:

    创建一个新的 web 视图


显示 UI 面板

  • - webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:

    显示一个 JavaScript 警告面板。

  • - webView:runJavaScriptConfirmPanelWithMessage:initiatedByFrame:completionHandler:

    显示一个 JavaScript 确认面板。

  • - webView:runJavaScriptTextInputPanelWithPrompt:defaultText:initiatedByFrame:completionHandler:

    显示一个 JavaScript 文本输入面板。


关闭 web 视图

  • webViewDidClose:

    通知您的应用程序 DOM 窗口成功关闭。


显示上传面板

  • - webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:

    显示文件上传面板。


响应强制触控动作

  • - webView:shouldPreviewElement:

    确定给定元素是否应显示预览。

  • - webView:previewingViewControllerForElement:defaultActions:

    当用户执行窥视操作时调用。

  • - webView:commitPreviewingViewController:

    当用户在预览中执行弹出操作时调用。


OC 原生显示 JS 弹窗的正确方法

参考: WKWebView completionHandler called before dismissal @stackoverflow

// 显示一个 JavaScript 警告弹窗
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {

    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"AlertPanel" message:message preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction *alertAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {

        // ⚠️必须在这里回调 JavaScript 
        completionHandler();

    }];
    [alertController addAction:alertAction];
    [self presentViewController:alertController animated:YES completion:nil];

}

WKUIDelegate 示例代码:

#pragma mark - WKUIDelegate

// 创建新的webView
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
    WKFrameInfo *frameInfo = navigationAction.targetFrame;
    if (![frameInfo isMainFrame]) {
        [webView loadRequest:navigationAction.request];
    }
    return nil;
}

// 显示 JavaScript 警告框
// 测试JS代码:alert("alert message!")
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    [UIAlertController showAlertInViewController:self withTitle:@"温馨提示" message:message cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:@[@"确认"] tapBlock:^(UIAlertController * _Nonnull controller, UIAlertAction * _Nonnull action, NSInteger buttonIndex) {
        completionHandler();
    }];
}

// 确认框
// 测试JS代码:confirm("confirm message!")
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
    [UIAlertController showAlertInViewController:self withTitle:@"需要再次确认" message:message cancelButtonTitle:@"取消" destructiveButtonTitle:nil otherButtonTitles:@[@"确认"] tapBlock:^(UIAlertController * _Nonnull controller, UIAlertAction * _Nonnull action, NSInteger buttonIndex) {
        if (buttonIndex == 0) {
            completionHandler(NO);
        }else {
            completionHandler(YES);
        }
    }];
}

WKScriptMessageHandler API

遵守 WKScriptMessageHandler 协议的类,提供了从网页中运行 JavaScript 接收消息的方法。

// 从 web 界面中接收到一个脚本时调用
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

这个协议只包含以上一个必须实现的方法,这个方法是提高 App 与 web 端交互的关键,它可以直接将接收到的 JS 脚本转为 OC 或 Swift 对象。(当然,在 UIWebView 也可以通过 “曲线救国” 的方式与 web 进行交互,著名的 Cordova 框架就是这种机制)

——WKWebView 的新特性与使用 @saitjr

这个 API 真正神奇的地方在于 JavaScript 对象可以自动转换为 Objective-C 或 Swift 对象。

——WKWebView @nshipster

在 JavaScript 端通过 window.webkit.messageHandlers.{InjectedName}.postMessage() 方法来发送消息到 native。我们需要遵守该协议,然后实现代理方法,就可以收到消息,并做相应的的处理。

如果在开始时就注入很多的名称,就需要区分处理

if (message.name == “AppModel” ) {

//…

}

——WKWebView 特性及使用 @360doc 【Swift】

WKUserScript API

WKUserScript 对象表示可以注入到网页中的脚本。

Symbols

初始化脚本

  • -initWithSource:injectionTime:forMainFrameOnly:

    返回可以添加到用户内容控制器中的初始化用户脚本。


检查脚本信息

  • source

    脚本源代码

  • injectionTime

    脚本应该被注入网页中的时间点。

  • forMainFrameOnly

    布尔值,指示脚本是否仅应注入主框架(是)或所有框架(否)。


常量

  • WKUserScriptInjectionTime

    将用户脚本注入网页的时间。

typedef enum WKUserScriptInjectionTime : NSInteger {
    WKUserScriptInjectionTimeAtDocumentStart, // 在文档元素创建之后,但在加载任何其他内容之前注入脚本。
    WKUserScriptInjectionTimeAtDocumentEnd //在文件完成加载后,但在其他子资源完成加载之前注入该脚本。
} WKUserScriptInjectionTime;

使用示例

WKUserScript 对象可以以 JavaScript 源码形式初始化,初始化时还可以传入时间是在加载之前还是结束时注入,以及脚本影响的是这个布局还是仅主要布局。于是用户脚本被加入到 WKUserContentController 中,并且以 WKWebViewConfiguration 属性传入到 WKWebView 的初始化过程中。

- (void)viewDidLoad {
    [super viewDidLoad];

    // 使用 WKUserScript 注入JavaScript脚本
    // 图片缩放的js代码
    NSString *js = @"var count = document.images.length;for (var i = 0; i < count; i++) {var image = document.images[i];image.style.width=320;};window.alert('找到' + count + '张图');";

    // 根据JS字符串初始化 WKUserScript 对象
    WKUserScript *script = [[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];

    // 根据生成的 WKUserScript 对象,初始化 WKWebViewConfiguration 对象
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    [config.userContentController addUserScript:script];

    // 初始化 WKWebView 对象
    _webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
    _webView.UIDelegate = self;
    _webView.navigationDelegate = self;
    [_webView loadHTMLString:@"<head></head>![](https://blog-andy0570-1256077835.cos.ap-shanghai.myqcloud.com/site_Images/071433.jpg)" baseURL:nil];
    [self.view addSubview:_webView];

}

使用 WKWebView

WKWebView 新特性

加载 web 页面

加载本地资源

  • - loadHTMLString:baseURL:

    同步方式加载本地资源,数据可以来源于本地文件或者硬编码的 HTML 字符串

//设定主页文件的基本路径,通过一个HTML字符串加载主页数据
- (IBAction)loadHTMLString:(id)sender {

    //主页文件名
    NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"Baidu001"
                 ofType:@"html"];
    //主页文件的基本路径
    NSURL *bundleUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];

      //将 index.html 文件的内容读取到 NSString 对象中
    NSError *error = nil;  
    NSString *html = [[NSString alloc] initWithContentsOfFile:htmlPath
                                                     encoding:NSUTF8StringEncoding
                                                        error:&error];
    //数据加载没有错误的情况下
    if (error == nil) {
        [self.webView loadHTMLString:html baseURL:bundleUrl];
    } 
}
  • - loadData:MIMEType:characterEncodingName:baseURL:

    指定 MIME 类型、编码集和 NSDate 对象加载一个主页数据,并设定主页文件基本路径

//NSData是一种二进制的字节数组类型。它没有字符集的概念,但用它来装载webView的时候必须指定字符集。
- (IBAction)loadDATA:(id)sender {

    NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"index"
                 ofType:@"html"];
    NSData *htmlData = [[NSData alloc] initWithContentsOfFile:htmlPath];

    NSURL *bundleUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];

    [self.webView loadData:htmlData
                  MIMEType:@"text/html"
          textEncodingName:@"UTF-8"
                   baseURL:bundleUrl];

}
  1. 加载本地 HTML:iOS9 以上的系统可以使用 WKWebView loadFileURL:allowingReadAccessToURL:,iOS9 以下的版本没有这个方法,需要先将本地 HTML 文件的复制到 tmp 目录中,然后使用 loadRequest: 方法来加载。如果在 HTML 中引入其他资源文件,例如 js,css,image 等必须一同复制到 temp 目录中

    ——WKWebview 开发笔记 @Qin’s Blog

加载网络资源

// 创建 WKWebView 对象
CGRect rect = CGRectMake(0, 0, self.view.width, self.view.height);
WKWebView *webView = [[WKWebView alloc] initWithFrame:rect];
// 设置导航代理,监听网页加载进程
_webView.navigationDelegate = self;
// 将 webView 对象添加到视图
[self.view addSubview:webView];
// 根据URL发起网络请求
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"www.google.com"]];
[webView loadRequest:request];

创建可与 JavaScript 交互的 WKWebView

1. Objective-C 调用 JavaScript 方法

  • JS 方法:

    <!-- JavaScript 方法, 接收一个参数 --> 
    function alertAction(message) {
      alert(message);
    }
  • Objective-C 原生调用

    // OC 调用 JS 的方法
    [self.mainWebView evaluateJavaScript:@"alertAction('OC调用JS方法时传入的参数')" completionHandler:^(id _Nullable item, NSError * _Nullable error) {
      NSLog(@"alert");
    }];

2. JavaScript 调用 Objective-C 方法

参考:WKWebView 的使用和各种坑的解决方法(OC+Swift)

  • 第一步:
- (WKWebViewConfiguration *)webViewConfiguration {
    if (!_webViewConfiguration) {

        // 创建 WKWebViewConfiguration 对象
        _webViewConfiguration = [[WKWebViewConfiguration alloc] init];

        // 创建 WKUserContentController 对象,提供 JavaScript 向 webView 发送消息的方法
        WKUserContentController *userContentColtroller = [[WKUserContentController alloc] init];
        // 添加消息处理,注意:self指代的对象需要遵守 WKScriptMessageHandler 协议,结束时需要移除
        [userContentColtroller addScriptMessageHandler:self name:@"close"];
        // 将 userConttentController 设置到配置文件
        _webViewConfiguration.userContentController = userContentColtroller;

    }
    return _webViewConfiguration;
}

ScriptMessageHandler 的 name 必须和 web 中的 JavaScript 方法名称一致:

window.webkit.messageHandlers.close.postMessage("message");

<script type="text/javascript">

    function doPrint() {
        if (checkiOS() == true) {
            // iOS 注入代码
            window.webkit.messageHandlers.scriptName.postMessage("script message!");
        } else {
            // Android 注入代码
            Android.startLHYL();
        }
    }

    // Android/iOS设备判断
    function checkiOS() {

        var u = navigator.userAgent;

        var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; // Android
        var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);             // iOS

        if (isiOS) {
            return true;
        } else {
            return false;
        }
    }

</script>
  • 第二步:
#pragma mark - WKScriptMessageHandler

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {

    NSLog(@"%s",__FUNCTION__);

    if ([message.name isEqualToString:@"close"]) {

        NSLog(@"WKScriptMessage:%@",message.body);
    }
}
  • 第三步:
- (void)dealloc { 

    // 移除 ScriptMessageHandler
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"close"];
}

显示加载进度条

对于 WKWebView ,有三个属性支持 KVO,因此我们可以监听其值的变化,分别是:loadingtitleestimatedProgress, 对应功能表示为:是否正在加载中、页面的标题、页面内容加载进度(值为 0.0~1.0)

刚开始用的方法:WKWebView 添加进度条 ,参照着写完发现如果网速好的话,会出现进度条走不完就被隐藏的现象。
解决方法:添加延时 animation 动画,参考:iOS WKWebView 添加网页加载进度条

代码如下:

//
//  OSCViewController.m
//  2015-01-31-WKWebViewDemo
//
//  Created by TangJR on 15/1/31.
//  Copyright (c) 2015年 tangjr. All rights reserved.
//

#import "OSCViewController.h"
#import <WebKit/WebKit.h>

@interface OSCViewController () <WKNavigationDelegate>

@property (nonatomic, strong) NSURL *URL;
@property (nonatomic, strong) WKWebViewConfiguration *webViewConfiguration;
@property (nonatomic, strong) WKWebView *webView;

/** 1️⃣ 添加 progressView 属性*/
@property (nonatomic, strong) UIProgressView *progressView;

@end

@implementation OSCViewController

#pragma mark - Lifecycle

- (void)loadView {
    [super loadView];
    self.view = self.webView;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // 3️⃣ 将 progressView 添加到父视图
    [self.view addSubview:self.progressView];

    // 4️⃣ 使用 KVO 注册观察者
    // 监听 WKWebView 对象的 estimatedProgress 属性,就是当前网页加载的进度
    [self.webView addObserver:self
                   forKeyPath:@"estimatedProgress"
                      options:NSKeyValueObservingOptionNew
                      context:nil];

    NSURLRequest *request = [NSURLRequest requestWithURL:self.URL];
    [self.webView loadRequest:request];
}

- (void)dealloc {
    // 6️⃣ 移除观察者
    [self.view removeObserver:self forKeyPath:@"estimatedProgress"];

}
#pragma mark - Custom Accessors

- (NSURL *)URL {
    if (!_URL) {
        _URL = [NSURL URLWithString:@"http://www.oschina.net/"];
    }
    return _URL;
}

- (WKWebViewConfiguration *)webViewConfiguration {
    if (!_webViewConfiguration) {
        _webViewConfiguration = [[WKWebViewConfiguration alloc] init];
    }
    return _webViewConfiguration;
}

- (WKWebView *)webView {
    if (!_webView) {
        _webView = [[WKWebView alloc] initWithFrame:[[UIScreen mainScreen] bounds] configuration:self.webViewConfiguration];
        // 设置导航代理,监听网页加载进程
        _webView.navigationDelegate = self;
    }
    return _webView;
}

/**
 2️⃣ 初始化progressView

 @return 返回初始化后的进度条视图
 */
- (UIProgressView *)progressView {
    if (!_progressView) {
        CGRect sreenBounds = [[UIScreen mainScreen] bounds];
        CGRect progressViewFrame = CGRectMake(0, 64, sreenBounds.size.width, 1);
        _progressView = [[UIProgressView alloc] initWithFrame:progressViewFrame];
        // 设置进度条色调
        _progressView.tintColor = [UIColor blueColor];
        // 设置进度条跟踪色调
        _progressView.trackTintColor = [UIColor whiteColor];
    }
    return _progressView;
}

#pragma mark - KVO

// 5️⃣ 接收变更后的通知,计算 webView 的进度条
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                       context:(void *)context {

    if ([keyPath isEqualToString:@"estimatedProgress"]) {

        self.progressView.progress = self.webView.estimatedProgress;

        if (self.progressView.progress == 1) {
            /*
             * 添加一个简单的动画,将 progressView 的 Height 变为1.5倍
             * 动画时长0.25s,延时0.3s后开始动画
             * 动画结束后将 progressView 隐藏
             */
            __weak __typeof(self)weakSelf = self;
            [UIView animateWithDuration:0.25f delay:0.3f options:UIViewAnimationOptionCurveEaseInOut animations:^{
                weakSelf.progressView.transform = CGAffineTransformMakeScale(1.0f, 1.5f);
            } completion:^(BOOL finished) {
                weakSelf.progressView.hidden = YES;
            }];
        }

    }else {
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}

#pragma mark - WKNavigationDelegate

//  页面开始加载web内容时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
    self.progressView.hidden = NO;
    // 防止 progressView 被网页挡住
    [self.view bringSubviewToFront:self.progressView];
}

//  当web内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {

}

//  页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {

}

//  页面加载失败时调用 ( 【web视图加载内容时】发生错误)
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
    NSLog(@"Error:%@",error.localizedDescription);
    self.progressView.hidden = YES;
}

// 【web视图导航过程中发生错误】时调用。
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
    NSLog(@"Error:%@",error.localizedDescription);
    self.progressView.hidden = YES;

    // 如果请求被取消
    if (error.code == NSURLErrorCancelled) {
        return;
    }
}

// 当Web视图的Web内容进程终止时调用。
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
    self.progressView.hidden = YES;
}

@end

监听 webView 的 title 属性并设置标题

- (void)viewDidLoad {
    [super viewDidLoad];

    // ------ 监听 WKWebView 对象的 title 属性
    [self.webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];

    NSURLRequest *request = [NSURLRequest requestWithURL:self.URL];
    [self.webView loadRequest:request];
}

- (void)dealloc {
    // ------ 移除观察者
    [self.view removeObserver:self forKeyPath:@"title"];
}

#pragma mark - KVO

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                       context:(void *)context {

     if ([keyPath isEqualToString:@"title"]) {
        self.title = change[@"new"];
    }else {
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    }
}

URL 中文处理

// URL 中文处理
- (NSURL *)URL {
    if (!_URL) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdeprecated-declarations"
        _URL = [NSURL URLWithString:(NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)@"https://www.oschina.net/ios/home", (CFStringRef)@"!$&'()*+,-./:;=?@_~%#[]", NULL,kCFStringEncodingUTF8))];
#pragma clang diagnostic pop
    }
    return _URL;
}

导航栏右侧添加刷新按钮

#pragma mark - Lifecycle

- (void)viewDidLoad {
    [super viewDidLoad];

    // 刷新按钮
    UIBarButtonItem *rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(rightBarButtonDidClicked:)];
    self.navigationItem.rightBarButtonItem = rightBarButtonItem;
}

#pragma mark - IBActions

- (void)rightBarButtonDidClicked:(id)sender {
    [self.webView reload];
}

禁用长按选中文字效果

https://segmentfault.com/q/1010000006649503

- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {

    // 禁用选中效果
    [self.webView evaluateJavaScript:@"document.documentElement.style.webkitUserSelect='none'" completionHandler:nil];
    [self.webView evaluateJavaScript:@"document.documentElement.style.webkitTouchCallout='none'" completionHandler:nil];
}

其他

  • 基于 UIWebView 的加载进度条开源框架:NJKWebViewProgress

加载 HTML 图片大小自适应

参考 WebView 加载 HTML 图片大小自适应与文章自动换行

在 HTML 代码中设置内容样式,一般使用 css 或者 js ,根据加载优先级以及加载效果,可以自行选择。

  • js 在页面加载完之后加载,所以设置图片样式的时候,会先加载大图,然后突然变小;
  • css 在引入时加载,直接加载缩小的图片(实际占用内存不会缩小);

一:使用 css 进行图片的自适应

#define HTML_NO_HEAD @"![](https://blog-andy0570-1256077835.cos.ap-shanghai.myqcloud.com/site_Images/071438.jpg)<br/>![](https://blog-andy0570-1256077835.cos.ap-shanghai.myqcloud.com/site_Images/071440.jpg)<br/>"

#define HTML_HAD_HEAD @"<head></head><body>![](https://blog-andy0570-1256077835.cos.ap-shanghai.myqcloud.com/site_Images/071438.jpg)<br/>![](https://blog-andy0570-1256077835.cos.ap-shanghai.myqcloud.com/site_Images/071440.jpg)<br/></body>"

- (void)viewDidLoad {
    [super viewDidLoad];

    // 没有<head>标签
//    [self.webView loadHTMLString:[self reSizeImageWithHTMLNoHead:HTML_NO_HEAD] baseURL:nil];

    // 有<head>标签
    [self.webView loadHTMLString:[self reSizeImageWithHTMLHadHead:HTML_HAD_HEAD] baseURL:nil];
}

/    ----------------------------------------------------------------
// 1.图片样式:不管用户以前设置的图片尺寸是多大,都缩放到宽度为320px大小。

// 如果后台返回的HTML代码中,不包含 <head> 标签,则可以直接在HTML字符串前拼接代码。
- (NSString *)reSizeImageWithHTMLNoHead:(NSString *)html {

    return [NSString stringWithFormat:@"<head><style>img{width:320px !important;}</style></head>%@", html];
}

// 如果包含 <head> 标签,则在<head>标签内部替换添加
- (NSString *)reSizeImageWithHTMLHadHead:(NSString *)html {

    return [HTML_HAD_HEAD stringByReplacingOccurrencesOfString:@"<head>" withString:@"<head><style>img{width:320px !important;}</style>"];
}

/    ----------------------------------------------------------------
// 2.图片样式:若需要根据图片原本大小,宽度小于320px的不缩放,大于320px的缩小到320px,那么在HTML字符串前加上一下代码:
- (NSString *)reSizeImageWithHTML:(NSString *)html {

    return [NSString stringWithFormat:@"<head><style>img{max-width:320px !important;}</style></head>%@", html];
}

二:使用 js 进行图片的自适应

在 webview 的代理中,执行 js 代码。(下面这段代码是必须有标签的)

如果没有标签,也很简单,只需要给返回的 HTML 字符串前面拼接一个即可。

- (void)webViewDidFinishLoad:(UIWebView *)webView
 {
    [webView stringByEvaluatingJavaScriptFromString:
     @"var script = document.createElement('script');"
     "script.type = 'text/javascript';"
     "script.text = \"function ResizeImages() { "
         "var myimg,oldwidth,oldheight;"
         "var maxwidth=320;"// 图片宽度
         "for(i=0;i <document.images.length;i++){" "myimg = document.images[i];" "if(myimg.width > maxwidth){"
                 "myimg.width = maxwidth;"
             "}"
         "}"
     "}\";"
     "document.getElementsByTagName('head')[0].appendChild(script);"];
    [webView stringByEvaluatingJavaScriptFromString:@"ResizeImages();"];
}

文章内容自动换行

文章的自动换行也是通过 css 实现的,书写方式图片缩放类似。在没有标签的情况下,在 HTML 代码前,直接拼接以下代码即可(若包含,则将代码添加到 body 标签内部),意思是全部内容自动换行。

<body width=320px style=\"word-wrap:break-word; font-family:Arial\">

参考

  • ShingoFukuyama/WKWebViewTips
  • 浅谈 WKWebView 使用、JS 的交互
  • iOS 新闻类 App 内容页技术探索
  • WKWebView 在实际开发中的使用汇总 【URL 中文处理、监听 estimatedProgress、title、H5 通过原生方法调用相册】
  • WKWebView @nshipster 【 Swift】
  • WKWebView 的新特性与使用 @saitjr
  • 使用 safari 对 webview 进行调试 @saitjr
  • WebView 加载 HTML 图片大小自适应与文章自动换行 @saitjr
  • 还在用 UIWebView? 何不试试 WKWebView @吴白
  • WKWebview 开发笔记 @Qin’s Blog 【清理缓存?】
  • WKWebView 特性及使用 @360doc 【Swift】
  • 带你走进 WKWebView 的世界 @SOI
  • WKWebView 的使用和各种坑的解决方法(OC+Swift)
  • js (javascript) 与 ios (Objective-C) 相互通信交互 @天狐博客
  • WKWebView 使用及注意点 (keng) @伯乐在线
  • ShingoFukuyama /WKWebViewTips
  • IOS 进阶之 WKWebView @ 翻滚的牛宝宝
  • iOS8 WebKit 库之 ——WKWebView 篇
  • iOS WebViewJavascriptBridge 简单运用方法 @iSuAbner
  • iOS 下 JS 与 OC 互相调用(六)–WKWebView + WebViewJavascriptBridge
  • WK 与 JS 的那些事
  • API 翻译:UIWebView (Swift 版)
  • API 翻译:WebKit
  • API 翻译:WKWebView (Swift 版)

欢迎关注我的其它发布渠道