iOS基础之webview

iOS中的Webview与Android中的Webview有一些差异,包括网络权限、加载页面、与JS交互等。不过大体使用思路是类似的,下面总结一下iOS中的Webview。

<!--more-->

参考:

1. 概述

现在iOS提供了两套webview:UIWebViewWKWebView。根据官方文档的建议,iOS8之后建议使用WKWebView

Starting in iOS 8.0 and OS X 10.10, use WKWebView to add web content to your app. Do not use UIWebView or WebView.

由于历史原因,我司iOS客户端貌似二者均有采用。因此不可避免地均需要了解一下~~。先看看最新的WKWebView吧。

2. WKWebView

2.1. 使用方式

基本的使用方式是:构建请求对象,实例化webview对象,然后发送请求即可

- (void)setWKWebview{
      // 加载路径
    NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"];
    // 构造请求对象
    NSURLRequest *request =[NSURLRequest requestWithURL:url];
    // 实例化webview
    WKWebView *webview = [[WKWebView alloc] initWithFrame:self.view.bounds];
    // 将webview加载至当前视图
    [self.view addSubview:webview];
      // 发送请求
    [webview loadRequest:request];
}

记得引入#import<WebKit/WebKit.h>文件。

2.2. 本地服务器

在iPhone模拟器上,可以直接在safari中通过localhost:port访问本地服务器。

在Android是通过10.0.2.2:port来访问本地服务器的。

2.3. 请求http报错

在测试加载本地服务器文件时一直显示页面空白,找了半天也没有发现原因(没有任何错误也没有任何警告),后来测试iOS的网络请求时发现下面的警告

App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.

可以在info.plist中修改相关的权限,参考https://stackoverflow.com/questions/31254725/transport-security-has-blocked-a-cleartext-http.

具体步骤:

  • 在资源管理器打开info.plist,增加一项App Transport Security Settings,选择数据类型为Dictionary
  • App Transport Security Settings下增加一项Allow Arbitrary Loads,选择类型为Boolean,值为YES

info.plistAndroidManifest.xml功能相似,用来修改程序的一些基础配置,这个后面再了解。

2.4. 添加加载进度条

在手机QQ中打开网页可以看见一个渐变的进度条,非常漂亮,实际上WKWebview的进度条是可以定制的。参考:iOS WKWebView添加网页加载进度条

3. JavaScript交互

WKWebview与JS的交互十分简单,调用webview对象的evaluateJavaScript即可。

由于是测试Demo,为了减少文件数,我们将webview的navigationDelegate代理指向当前视图控制器自身,

这是因为我们需要使用navigationDelegate代理的didFinishNavigation方法。

- (void)setWKWebview{
    // 初始化webview对象 
    webview.navigationDelegate = self;
    // ...
}

3.1. 执行JavaScript代码

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    [webView evaluateJavaScript:@"document.body.style.backgroundColor = 'blue'" completionHandler:^(id result, NSError *error) {
        NSLog(@"Result %@", result);
    }];
}

上述代码从字面上很容易理解,页面加载完成执行一串JavaScript代码。关于WKNavigationDelegate更多可参考这里。此外如果需要测试alert等方法,需要实现WKUIDelegate代理。

evaluateJavaScript方法接受两个参数,需要执行的JS代码,以及执行完毕后的回调,执行完的JS表达式值会作为参数传递给该回调函数(比如上面的代码返回blue字符串)。

3.2. JavaScript调用原生代码

实际上目前工作中接触到的项目,很少有通过OC去执行JS代码的。相反地,一般是通过原生实现接口然后封装成SDK提供给JS,比如跳转原生界面、分享页面等。这是通过WKScriptMessageHandler协议来实现的。

WKScriptMessageHandler依赖于两个类:

  • WKUserContentController,用来向页面注入相关的原生接口,也可以直接向页面注入JS代码貌似
  • WKScriptMessage,可以理解为JS向原生方法传递的参数负载

为了实现调用原生的功能,在实例化webview时需要额外的一些配置,以便传入上面的两个类

- (void)setWKWebview{
    NSURL *url = [NSURL URLWithString:@"http://localhost:9999"];
    NSURLRequest *request =[NSURLRequest requestWithURL:url];

    // 定义JS可调用的原生方法
    WKUserContentController *userContentController = [[WKUserContentController alloc] init];
    [userContentController addScriptMessageHandler:self name:@"nativeTest"];

    // 配置webview,此处使用上面实例化的userContentController对象
    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    configuration.userContentController = userContentController;

    // 传入配置
    WKWebView *webview = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];

    [self.view addSubview:webview];
    [webview loadRequest:request];
}

然后实现一个通用的消息接收器,根据消息携带的方法名和参数,执行对应的原生方法,达到JS调用原生的目的

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
      // message 包含了对应的方法名和参数
    NSLog(@"方法名:%@", message.name);
    NSLog(@"参数:%@", message.body);
    // 方法名
    NSString *methods = [NSString stringWithFormat:@"%@:", message.name];
    SEL selector = NSSelectorFromString(methods);
    // 调用方法
    if ([self respondsToSelector:selector]) {
        [self performSelector:selector withObject:message.body];
    } else {
        NSLog(@"未定义方法:%@", methods);
    }
}

做完准备工作,接下来实现相关的原生方法,这里的方法在上面addScriptMessageHandler处声明。

- (void)nativeTest:(NSString *)str {
    // 可以在原生中做许多事情...
    NSLog(@"Hello this is OC");
    NSLog(str);
}

最后就是web端调用相关的接口的了

btn.onclick = function(){
      // window.webkit.messageHandlers 为注入的全局对象,携带相关的接口方法
    console.log(window.webkit.messageHandlers)
      // nativeTest接口被实现为一个对象,通过postMessage方法调用原生方法并传递相关参数
    window.webkit.messageHandlers.nativeTest.postMessage('Hello from JS')
}

这样在userContentController的message中,对应的message.name即为‘nativeTest’,而message.body的值即为'Hello from JS',大功告成。

还有个需要注意的问题就是JS传递参数的类型,一般来讲OC会接收一个dist类型的参数,对应JS的对象,所以十分方便。

4. 调试

参考iOS调试UIWebView和WKWebView加载的H5页面

4.1. 模拟器调试

在模拟器上调试页面十分简单

  • 打开模拟器,然后打开模拟器中的safari,打开需要调试的页面网址
  • 打开Mac上的safari,在“Devalop > iOS Simulator”菜单下,会出现模拟器中已打开的网页列表,选中模拟器中打开的页面就可以调试了

这里貌似跟webview没有多大关系,实际上app中的webview页面也可以通过这种方式进行调试,十分方便~

最近在项目中遇见了一个比较神奇的BUG,在Chrome的分辨率模拟器中功能是正常的,而在iPhone的浏览器中却显示不正常,然后通过模拟器打开居然复现了这个BUG,然后根据safari调试很快就定位了问题然后得以解决,说明这个iPhone模拟器跟真机还是很接近的。

如果打开Safari,在Develop菜单下没有找到iOS Simulator的选项,可以试试重启Safari,这个貌似是个BUG

唯一的问题是需要掌握safari浏览器下的调试方法,与Chrome还是有一些区别的(有点不太习惯)。

4.2. 真机调试

查资料的时候发现历史上使用真机调试需要注册$99的开发者账号,然后下载调试证书啥的...幸好现在2017年都快结束了,好了不说废话了,下面说说操作步骤。

获得调试证书

首先打开XCode,选择Preferences->Accounts,绑定Apple ID,然后点击Manage Certificates,新增一个调试证书即可

关联设备

通过USB连接iPhone真机,选择Window->Devices and Simulators,可以查看到已关联的设备,貌似现在可以支持无线设备调试了,具体可以参考这里,不过我没有发现相关选项~

选择运行平台

这里有一篇文章,对iOS项目的运行做了比较详细的介绍。

XCode项目编译方案和运行目标是分开的,我们只需要在切换编译右侧的运行平台,选择对应的设备或模拟器运行项目即可。

这里遇见了几个问题:

  • 项目的目标版本和真机的系统版本不一致,可以通过修改项目的Deployment Target版本号调整,整个项目的设置可以通过左侧资源管理器中,点击项目目录名称打开(那个蓝色的xcode图标)
  • requires a development team错误,原因是项目Signing设置下的Team默认为空导致的,选择一个开发组即可。
  • 在真机上提示“您的设备管理设置不允许在此台iPhone上使用开发者XX的应用”,这个可以通过iPhone设置->描述文件->开发者文件->信任即可。

好吧,上面的操作基本上都是针对iOS真机调试,不过在真机上调试webview页面,这些步骤也是必备的。

5. 小结

至此对于iOS中的webview已有大致了解,最后的工作就是完成iPhoneX的页面样式适配~这是学习OC和WKWebview的主要目的。

此外关于OC中的一些语法,比如代理、回调函数、选择器等,需要进一步了解。

Vue源码阅读笔记之项目结构和Vue对象(一) Objective-C基础入门