iOS WKWebview实战

一、配置

1、初始化

导入头文件#import<WebKit/WebKit.h>

#pragma mark - LazyLoad
- (WKWebView *)webView {
    if (!_webView) {
        WKUserContentController *userContentController = [[WKUserContentController alloc] init];
        [userContentController addScriptMessageHandler:handler name:@"XXX"];//对象名称
        WKWebViewConfiguration  *configuration         = [[WKWebViewConfiguration alloc] init];
        configuration.userContentController = userContentController;

        _webView                                     = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
        _webView.navigationDelegate                  = self;
        _webView.UIDelegate                          = self;
        _webView.backgroundColor                     = [UIColor clearColor];
        _webView.allowsBackForwardNavigationGestures = YES;
        _webView.scrollView.alwaysBounceVertical     = YES;
    }
    return _webView;
}

2、delegate

WKNavigationDelegate:

和UIWebViewDelegate功能类似,提供加载过程操作、跳转等功能

// 开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
// 页面加载完调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error ;
// 内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation ;
// 在发送请求之前,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
// 在收到响应后,决定是否跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
// 加载 HTTPS 的链接,需要权限认证时调用  \  如果 HTTPS 是用的证书在信任列表中这不要此代理方法
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;

WKUIDelegate:

与UI界面相关,通过此delegate调用原生提示框比较常用

#pragma mark - WKUIDelegate
// 提示框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler ;
// 确认框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler ;
// 输入框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler;

3、KVO

WKWebView可通过KVO监听属性 title、estimaredProgress来设置VC的title和进度条

#pragma mark - KVO
- (void)addObserverForWebView {
    //实现显示HTML中写好的title
    [self.webView addObserver:self
                   forKeyPath:@"title"
                      options:NSKeyValueObservingOptionNew
                      context:nil];
    //实现加载过程中的进度条
    [self.webView addObserver:self
                   forKeyPath:@"estimatedProgress"
                      options:NSKeyValueObservingOptionNew
                      context:nil];
}

二、加载

常用到的加载方式基本和UIWebview一样。如果项目Info.plist中设置了App Transport Security,最好是使用https,非https则需要添加白名单,外链也要添加白名单!

1、网络加载

//通过url加载网络资源
NSURL *url = [NSURL URLWithString:@"https://www.baidu.com"];
[self.webView loadRequest:[NSURLRequest requestWithURL:url]];

2、加载HTMLString

通常需要加载的HTMLString,一般是从服务器传过来显示,要么就是固定写死在代码里的。当然,本地的HTML文件也可以转成HTMLString。

  //这里bundleUrl是mainBundle路径的url 如果是后台穿过来的htmlString直接传nil
   [self.webView loadHTMLString:string baseURL:bundleUrl];

3、加载本地HTML文件

这一步就取决于你拿到手的文件了,如果只有一个对应的HTML文件,样式都写在HTML中,不包含CSS、js文件( 类似iOS 不使用mvc全丢在Controller里),直接丢项目里,然后获取到文件在本地的url加载就行了。

    NSString *path =[[NSBundle mainBundle] pathForResource:@"test"  ofType:@"html"];
    url = [NSURL fileURLWithPath:path];

如果还有.CSS、.js、图片资源等,那就需要考虑资源路径读取的问题,因为前端JS CSS 的调用有严格的页面结构,不然就会碰到图片和样式加载不出来的现象。
![我们常用的导入方式][id]
[id]: http://upload-images.jianshu.io/upload_images/1324353-bfe0baa5e41ab006.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240&_=6197761/to/image "Optional title attribute"
Create groups 忽略文件目录(相对路径,不建议采用)
以Create groups方式加入工程的文件夹,文件夹下的文件在iOS沙盒中全都被保存在一个 mainBundle 根路径下,即不管加入项目的文件的目录结构如何,在 APP 中都可以通过 mainBundlePath/filename 来访问到,而原来的目录结构则不存在了。而 HTML 中的图片和 CSS 文件的引用方式写的则是绝对路径。因此 HTML 中的路径就不对,需要把HTML中引用文件代码也忽略掉文件目录。如下:

src="/public/comm/highcharts.min.js"
href="/public/css/reset.css"
src="/public/images/test@2x.png"

替换成忽略文件目录的样式

src="min.js"
href="reset.css"
src="ic_test@2x.png"

然后就可以进行加载了

NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"];
NSString *htmlString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
NSString *basePath = [[NSBundle mainBundle] bundlePath];
NSURL *baseURL = [NSURL fileURLWithPath:basePath];
[self.webView loadHTMLString:htmlString baseURL:baseURL]

这样替换的工作量可想而知,如果前端写的时候就是这么引用,而且无重名文件,那也是可以的。

Create folder references 引用按照文件目录(绝对路径)
以这种方式导入工程时,文件夹会是蓝色的,一眼瞟过去明显和其他文件不搭。但是丑归丑,问题好歹解决了。

    NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html" inDirectory:@"fileName/fileName"];
    NSURL *fileUrl = [NSURL fileURLWithPath: htmlPath];
    [self.webView loadRequest:[NSURLRequest requestWithURL:fileUrl]];

注意 HTML 文件路径一定要写对!
注意 HTML 文件路径一定要写对!
注意 HTML 文件路径一定要写对!

三、交互

1、传参

其实和JS交互都可以理解为传参,无非就是信号的传递,然后做出对应的处理。这里的传参指的是把参数拼接在url后面,类似get请求。一般都是比较简单的HTML需求。给网络HTML传参直接拼url就好,本地HTML稍微麻烦点,需要绕过fileURLWithPath:拼出URLString。

    NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"html"];;
    //    NSURL *fileUrl = [NSURL fileURLWithPath: htmlPath];//内部实现加file:
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"file://%@?minute=%ld", encodedPath, minute]];

2、原生调用JS方法

原生调用JS方法只需要传对应的string过去就可以了。

    [self.webView evaluateJavaScript:string completionHandler:^(id _Nullable response, NSError * _Nullable error) {
        //如果这里有error肯定是没成功
    }];

这里有遇到一个坑,前端那边要求把参数转成json,可是回调回来就是不成功,后面找到原因是前端那边的json需要去空格去换行...可能是序列化和反序列化过程规则不一致把。

3、js调用原生方法

注入对象

通常JS需要调用原生方法时,需要在初始化WKWebview时注入对象。这里的handler最好是单独写个工具类出来,来灵活处理响应事件,给VC减负。

[userContentController addScriptMessageHandler:ocjsHelper name:@"XXX"];//对象名称

在dealloc方法中需要remove掉注册的对象,否则会造成内存泄漏。

 [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"XXX"];

这里、当handler为self时,会导致dealloc不执行、造成内存泄漏,单独注册一个handler并不会出现这个问题,暂时猜想可能是内部循环引用的问题(PS:只是猜想。。。)。

JS端发送消息

window.webkit.messageHandlers.showSendMsg.postMessage('sendMessage'])

接收消息

handler实现WKScriptMessageHandler协议

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    //name 注入对象名(string) ,body和前端商定的数据类型  
    if ([message.name isEqualToString:@"XXX"] && [message.body isKindOfClass:[NSDictionary class]]) {  
        //对应的业务处理  
     }
}

四、扩展

1、Html本地文件统一管理

当项目中的HTML文件越来越多,而且在项目中HTML文件都是以绝对路径的方式导入(黄蓝配),那么HTML文件就需要统一管理了。iOS中bundle就为我们提供了很好的解决方案,简而言之bundle一个目录就是用来存放项目中的各种资源文件,包括代码。新建bundle的优势在于你能方便的操作资源文件。制作bundle点击这里

    //获取bundle的path
    NSString *resourcesBundlePath = [[NSBundle mainBundle] pathForResource:@"TestBundle" ofType:@"bundle"];
    //获取bundle路径下的文件path
    NSString *htmlPath = [[NSBundle bundleWithPath:resourcesBundlePath] pathForResource:@"webLoaclFile" ofType:@"html" inDirectory:@"fileCatalogue"];

2、修改web显示样式

WKWebview和UIWebview类似都可以实现对web样式的一些简单修改。

    //修改字体大小 300%
    [self.webView evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '300%'" completionHandler:nil];
    //修改字体颜色  #00bb00
    [self.webView evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextFillColor= '#00bb00'" completionHandler:nil];

3、HTML实现显示自定义字体

(1)如果是本地HTML,前端在文件内加入对应字体就行,不需要手机端再做处理。
(2)如果是从服务器获取,可以从服务器下载缓存,或者丢在本地,加载时把字体路径传给HTML也应该是可以的。
(3)字体丢在本地,然后再对获取的HTMLString进行处理也是可以的,应该比较麻烦。

4、WebViewJavascriptBridge

如果不想重复造轮子,可以试试这个库,支持WKWebview和UIWebview

cocoapods: pod 'WebViewJavascriptBridge', '~> 5.0.5'

github: https://github.com/marcuswestin/WebViewJavascriptBridge

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容