- 本文讲述的是通过 WKWebView 实现H5页面加载的vc基类BaseWebViewController.swift,主要实现功能:
(1)加载普通H5页面(不安全链接也能加载)
(2)获取H5页面的title
(3)进度条功能实现
(4)处理JS提示框、确认框、文本输入框
(5)H5页面加载失败时,显示NoNetworkView(网络出错提示图),并实现重新加载功能
(6)url重定向
(7)JS与OC的交互
- BaseWebViewController:
class BaseWebViewController: BaseViewController {
//MARK: - 属性
public lazy var webView: WKWebView = {
let configuration = WKWebViewConfiguration.init()
let webView = WKWebView.init(frame: CGRect.init(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height), configuration: configuration)
webView.navigationDelegate = self
webView.uiDelegate = self
return webView
}()
private lazy var progressView: UIProgressView = {//进度条
let progressView = UIProgressView.init(frame: CGRect.init(x: 0, y: 0, width: view.bounds.width, height: 3.0))
progressView.progress = 0.0
progressView.tintColor = UIColor.red
return progressView
}()
private var urlString: String?//url地址字符串
private var isProgressBarHidden: Bool = false//是否隐藏ProgressView
private var hasLoadedSuccess: Bool = false//是否已经加载成功过
private let JSHandlerName_share = "Share"//JS调iOS-分享
//MARK: - 生命周期
override func viewDidLoad() {
super.viewDidLoad()
self.prepareUI()
}
deinit {
if !isProgressBarHidden {
self.webView.removeObserver(self, forKeyPath: "estimatedProgress")
}
self.webView.navigationDelegate = nil
self.webView.navigationDelegate = nil
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: - 其他
/// webViewController的初始化配置
///
/// - Parameters:
/// - urlString: 地址字符串
/// - isNavigationBarHidden: 是否隐藏navigationBar
/// - isProgressBarHidden: 是否隐藏progressBar
public func config(urlString:String, isNavigationBarHidden:Bool = false, isProgressBarHidden:Bool = false) {
self.urlString = urlString
self.isNavigationBarHidden = isNavigationBarHidden
self.isProgressBarHidden = isProgressBarHidden
}
/// UI初始化
private func prepareUI() {
if !isProgressBarHidden {
self.webView.addSubview(self.progressView)//添加进度条
self.webView.addObserver(self, forKeyPath: "estimatedProgress", options: NSKeyValueObservingOptions.new, context: nil)//进度监听
}
self.view.addSubview(self.webView)
self.addJSHandlerNames()
self.loadH5()
if !isNavigationBarHidden {
//返回按钮点击事件自定义
self.customGoBack { [weak self] in
if (self?.webView.canGoBack)! {
self?.webView.goBack()
} else {
self?.navigationController?.popViewController(animated: true)
}
}
}
}
/// 加载H5
private func loadH5() {
guard self.urlString != nil else {
return
}
let url = NSURL.init(string: self.urlString!)
guard url != nil else {
return
}
let request = NSURLRequest.init(url: url! as URL)
self.webView.load(request as URLRequest)
}
/**获取监听的代理方法*/
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "estimatedProgress" && !isProgressBarHidden {
self.progressView.alpha = 1.0
self.progressView.setProgress(Float(webView.estimatedProgress), animated: true)
if(self.webView.estimatedProgress >= 1.0) {//进度条的值最大为1.0
UIView.animate(withDuration: 0.3, delay: 0.1, options: UIViewAnimationOptions.curveEaseInOut, animations: { () -> Void in
self.progressView.alpha = 0.0
}, completion: { (finished:Bool) -> Void in
self.progressView.progress = 0
})
}
}
}
}
2.WKNavigationDelegate:
//MARK: - WKNavigationDelegate
extension BaseWebViewController: WKNavigationDelegate {
//MARK: - 导航监听
/**在发送请求之前,决定是否跳转*/
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
//...(实现url重定向)
let urlStr = navigationAction.request.url?.absoluteString
print(urlStr!)
decisionHandler(.allow)
}
/**身份验证*/
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
completionHandler(URLSession.AuthChallengeDisposition.useCredential, nil)//设置为nil,不要证书验证
}
/**在收到响应后,决定是否跳转*/
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
let urlStr = navigationResponse.response.url?.absoluteString
print(urlStr!)
decisionHandler(.allow)
}
/**接收到服务器跳转请求之后调用*/
func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
}
/**WKNavigation导航错误*/
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
AJAlertTool.showCancel(message: error.localizedDescription)
NetworkDataViewManager.addNoNetworkView(toView: self.view, refreshBlock: { [weak self] in
if self?.hasLoadedSuccess == true {
self?.webView.reload()//如果网络加载成功过,直接reload
} else {
self?.loadH5()//如果网页加载一次都没有成功过,则重新发起请求
}
})
}
/**WKWebView终止*/
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
}
//MARK: - 页面内监听
/**页面开始加载时调用*/
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
}
/**当内容开始返回时调用*/
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
}
/**页面加载完成之后调用*/
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
NetworkDataViewManager.removeNoNetworkView(fromView: self.view)
if !isNavigationBarHidden {
self.navigationItem.title = self.webView.title
}
self.hasLoadedSuccess = true
}
/**页面加载失败时调用*/
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
AJAlertTool.showCancel(message: error.localizedDescription)
NetworkDataViewManager.addNoNetworkView(toView: self.view, refreshBlock: { [weak self] in
if self?.hasLoadedSuccess == true {
self?.webView.reload()//如果网络加载成功过,直接reload
} else {
self?.loadH5()//如果网页加载一次都没有成功过,则重新发起请求
}
})
}
}
3.WKUIDelegate:
//MARK: - WKUIDelegate
extension BaseWebViewController: WKUIDelegate {
/**WKWebView创建初始化加载的一些配置*/
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
//为防止系统阻止不安全的连接,需要实现下面的方法
//如果目标主视图不为空,则允许导航
if !(navigationAction.targetFrame?.isMainFrame != nil) {
self.webView.load(navigationAction.request)
}
return nil
}
/**JS提示框(ok)处理*/
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
AJAlertTool.showOK(message: message) {
completionHandler()
}
}
/**JS确认弹窗(cancel & ok)处理*/
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
AJAlertTool.showCancelOK(title: nil, message: message, cancelTitle: "取消", cancelBlock: {
completionHandler(false)
}, okTitle: "确定") {
completionHandler(true)
}
}
/**js中的文本输入处理*/
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
AJAlertTool.showTextInput(title: prompt, message: nil, defaultTextFieldMessage: defaultText, textFieldPlaceholder: nil, cancelTitle: "取消", cancelBlock: nil, okTitle: "确定") { (textFieldText) in
completionHandler(textFieldText)
}
}
}
4.WKScriptMessageHandler:
//MARK: - WKScriptMessageHandler
extension BaseWebViewController:WKScriptMessageHandler {
/**从web界面中接收到一个脚本时调用*/
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == JSHandlerName_share {
//分享相关操作...
}
//...
}
}
5.JS交互相关:
//MARK: - JS交互相关
extension BaseWebViewController {
//MARK: - JS 调用 OC
/// 添加处理脚本
private func addJSHandlerNames() {
self.webView.configuration.userContentController.add(self, name: JSHandlerName_share)
//...
}
//MARK: - OC 调用 JS
/// 调用JS处理的事件1
private func JSAction1() {
//让JS调用HTML文件中的“show()”函数
self.webView.evaluateJavaScript("show()", completionHandler: { (response, error) in
//(处理JS执行“show()”函数之后的操作)
})
}
//...
}
- 疑问1:
JS与OC如何进行交互?JS与OC之间的交互能做哪些事情?
答:
(1)如果H5页面的某些功能处理效果不太理想,即可通过JS调OC,例如:上传图片、调取相册、分享、支付等native处理效果更好的功能
(2)如果我们想要点击某个native按钮,让H5页面发生改变,即可OC调JS,比如异步加载大图功能等
- 疑问2:
JS交互与重定向,谁更好用?
答:
H5页面在执行location.href()函数后,使得OC端重新执行 decidePolicyFor navigationAction 函数,重新进行页面请求,实现重定向,在 decidePolicyFor navigationAction 函数中可以解析H5传递的url,根据OC与H5约定的规则,做相应操作,这样也能实现分享、调取相册等操作。重定向的缺点就是url参数解析比较复杂,而且只实现了“JS调OC”的功能,JS交互可以实现JS调OC,也能实现OC调JS,用法灵活简便。
6.如何获取H5网页的title、logo?
- 可以使用js方法获取网页的title、logo
/**页面加载完成之后调用*/
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
NetworkDataViewManager.removeNoNetworkView(fromView: self.view)
//获取标题
self.webView.evaluateJavaScript("document.title", completionHandler: { (response, error) in
let linkTitle = response as? String
})
//获取logo
self.webView.evaluateJavaScript("document.querySelector('link[rel=\"shortcut icon\"]').href", completionHandler: { (response, error) in
let linkLogo = response as? String
})
}
参考文章:
https://www.jianshu.com/p/60b9681dd8d2
https://www.jianshu.com/p/ab58df0bd1a1