ios WebView的使用

UIWebView和WKWebView的区别

UIWebView

加载速度慢、占用内存多,优化困难。

研究一下再补充

WKWebView

WKWebView是ios8之后出现的,性能比UIWebView快很多,占用内存少。

特性:
  • 在性能、稳定性、功能方面有很大提升。
  • 允许JavaScriptNitro库加载并使用(UIWebView受限制)
  • 支持了更多的HTML5特性
  • 高达60fps的滚动刷新率以及内置手势
  • 将UIWebViewDelegate与UIWebView重构成了14类与3个协议
WKWebView的基本用法:
  1. 加载网页
  2. 加载的状态回调
  3. 新的WKUIDelegate协议
  4. 动态加载并运行JS代码
  5. webview执行JS代码
  6. JS调用App注册过的方法
WKWebView相关类
类(使用中发现的)
  • WKWebView
  • WKUserScript
  • WKWebViewConfiguration
  • WKUserContentController
  • WKScriptMessage
协议(使用中发现的)
  • WKUIDelegate
  • WKNavigationDelegate
  • WKScriptMessageHandler

一、加载网页

加载网页或者HTML代码的方式和UIWebView相同

wkwebview = WKWebView()
let url = URL(string: urlStr)
let request = URLRequest(url: url!)
wkwebview.load(request)

二、加载的状态回调 (WKNavigationDelegate)

用来追踪加载过程(页面开始加载、加载完成、加载失败)的方法:

// 页面开始加载时调用
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
    print("页面开始加载时调用")
}

// 页面内容开始返回调用
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
    print("页面内容开始返回调用")
}

// 页面加载完成之后调用
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    print("页面加载完成之后调用")
}

// 页面加载失败时调用
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
    print("页面加载失败时调用")
}

页面跳转的代理方法:

// 在发送请求之前,决定是否跳转
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    print("在发送请求之前,决定是否跳转")
    decisionHandler(.allow)
}

// 在收到响应后,决定是否跳转
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    print("在收到响应后,决定是否跳转")
    decisionHandler(.allow)
}

// 接收到服务器跳转请求之后执行
func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
    print("接收到服务器跳转请求之后执行")
}

三、WKUIDelegate协议

这个协议主要用于WKWebView处理web界面的三种提示框(警告框、确认框、输入框),下面是警告框的例子:

/**
 *  web界面中有弹出警告框时使用
 *
 *  @param webview              实现代理webview
 *  @param message              警告框中的内容
 *  @param frame                主窗口
 *  @param completionHandler    警告框消失调用
 */
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
    print("界面弹出了警告框")
    print("警告框的内容:\(message)")
    completionHandler()
}

四、动态加载JS

用于在客户端内部加入JS代码,添加js_name的点击事件

let jsStr = "var js_name = document.getElementById('js_name');function haha() {window.alert(js_name.text)};js_name.addEventListener('click', haha, false);"
let script: WKUserScript = WKUserScript(source: jsStr, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
let configuration = WKWebViewConfiguration()
configuration.userContentController.addUserScript(script)

wkwebview = WKWebView(frame: CGRect.zero, configuration: configuration)
wkwebview.uiDelegate = self
wkwebview.navigationDelegate = self
let url = URL(string: urlStr)
let request = URLRequest(url: url!)
wkwebview.load(request)

wkwebview.allowsBackForwardNavigationGestures = true
self.view.addSubview(wkwebview)

五、webView执行JS代码

执行已经动态添加JS的haha()方法,原有的js方法也可以调用

// wkwebview执行JS代码
wkwebview.evaluateJavaScript("haha()") { (_, _) in
    print("完成执行js方法的回调")
}

六、JS调用App注册的方法

首先,WKWebView里面注册JS需要调用的方法,是通过WKUserContentController类下面的方法

open func add(_ scriptMessageHandler: WKScriptMessageHandler, name: String)

scriptMessageHandler是代理回调,JS调用name方法后,会回调WKScriptMessageHandler代理里的这个方法

public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)

message包含着请求的方法的名称和参数,message.name是调用的方法名称,message.body是调用的方法的参数

然后JS在调用方法的时候要用下面的方法

window.webkit.messageHandlers.<name>.postMessage(<messageBody>)

注意,name(方法名)是放在中间的,messageBody只能是一个对象,如果要传多个值,需要封装成数组,或者字典。

实例代码(关键代码)
func initWKWebView() {
    self.view.backgroundColor = UIColor.white
    
    // 动态添加JS代码,实现某个控件的点击事件
    let jsStr = "var stundet = {'name': 'potato'};var js_name = document.getElementById('js_name');function haha() {window.webkit.messageHandlers.testJS.postMessage(stundet);};js_name.addEventListener('click', haha, false);"
    let script: WKUserScript = WKUserScript(source: jsStr, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
    let configuration = WKWebViewConfiguration()
    configuration.userContentController.addUserScript(script)
    
    wkwebview = WKWebView(frame: CGRect.zero, configuration: configuration)
    wkwebview.uiDelegate = self
    wkwebview.navigationDelegate = self
    let url = URL(string: urlStr)
    let request = URLRequest(url: url!)
    wkwebview.load(request)
    
    wkwebview.allowsBackForwardNavigationGestures = true
    self.view.addSubview(wkwebview)
    
    wkwebview.snp.makeConstraints { (make) in
        make.top.equalTo(statusBarHeight + navigationBarHeight)
        make.left.right.bottom.equalToSuperview()
    }
    
    // 注册供JS调用的方法
    wkwebview.configuration.userContentController.add(self, name: "testJS")
}

func testJS(name: String) {
    print("js调用原生方法, 参数name:\(name)")
}

// 实现代理
extension WebViewViewController: WKScriptMessageHandler {
    // JS调用原生方法的处理
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        print("JS 调用了\(message.name)方法,传回参数\(message.body)")
        if message.name == "testJS" {
            let data: Dictionary = message.body as! Dictionary<String, Any>
            testJS(name:data["name"] as! String)
        }
        
    }
}

⚠️使用了WKScriptMessageHandler后发现会造成内存泄漏,当前页面没有销毁,deinit方法执行。

七、解决JS调用APP注册的方法造成的内存泄漏

内存泄漏主要是因为循环引用造成self无法销毁,于是定义一个弱引用的WKScriptMessageHandler

import UIKit
import WebKit

class WeakScriptMessageDelegate: NSObject, WKScriptMessageHandler {

    // 弱引用
    weak var delegate: WKScriptMessageHandler?
    
    init(_ delegate: WKScriptMessageHandler) {
        super.init()
        
        self.delegate = delegate
    }
    
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        
        self.delegate?.userContentController(userContentController, didReceive: message)
    }
}

// 扩展WKUserContentController
extension WKUserContentController {
    func addHandler(_ message: Any, name: String) {
        if let msg = message as? WKScriptMessageHandler {
            self.add(WeakScriptMessageDelegate(msg), name: name)
        }
    }
}

把原来注册供JS调用的方法代码改成

wkwebview.configuration.userContentController.add(WeakScriptMessageDelegate(self), name: "testJS")

或者

使用扩展添加的方法

wkwebview.configuration.userContentController.addHandler(self, name: "testJS")

在deinit中记得remove掉注册的方法

deinit {
    wkwebview.configuration.userContentController.removeScriptMessageHandler(forName: "testJS")
    print("WebViewViewController 销毁")
}

参考博客 - 感谢大神的贡献


好好学习,天天向上。<( ̄oo, ̄)/


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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • 前言 关于UIWebView的介绍,相信看过上文的小伙伴们,已经大概清楚了吧,如果有问题,欢迎提问。 本文是本系列...
    CoderLF阅读 8,856评论 2 12
  • 一、WKWebView简介 UIWebView自iOS2就有,WKWebView从iOS8才有,毫无疑问WKWeb...
    慌莫染阅读 3,950评论 0 4
  • 一. 创建: 二. 加载方式: 常用的网页加载方式 加载html格式的内容(html文件中加载和html格式字...
    fjytqiu阅读 4,816评论 0 11
  • 前言: web页面和app的直接的交互是很常见的东西,在ios8之前,用的是uiwebview,但是在ios8之后...
    qingchen91阅读 2,896评论 6 25