一、WKWebView的使用:
1、初始化
webView初始化:WKPreferences, WKUserContentController -> WKWebViewConfiguration -> WKWebView
這裏添加了三個代理,代理方法會在下面實現。
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
preferences.javaScriptCanOpenWindowsAutomatically = true // default value is NO in iOS and YES in OS X.
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences;
configuration.userContentController = WKUserContentController()
// 註冊JS發送消息的name (自定義,可以設置多個)
configuration.userContentController.add(self, name: "moxiaoyan") // 添加 WKScriptMessageHandler 代理
let webView = WKWebView(frame: CGRect(x: 0, y: (navigationController?.navigationBar.frame.maxY ?? 0) + 1, width: view.frame.size.width, height: view.frame.size.height/2), configuration: configuration)
webView.navigationDelegate = self // 添加 WKNavigationDelegate 代理
webView.uiDelegate = self // 添加 WKUIDelegate 代理
進度條:顯示頁面加載進度,加載完成後 hidden (不需要的可以跳過)
// 加載進度條
progressView = UIProgressView(progressViewStyle: .default)
progressView.progressTintColor = .green
progressView.trackTintColor = .lightGray
progressView.progress = 0.0
progressView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 1)
webView.addSubview(progressView)
// 添加觀察者
webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "estimatedProgress" {
progressView.setProgress(Float(webView.estimatedProgress), animated: true)
if webView.estimatedProgress >= 1.0 {
progressView.isHidden = true
}
}
}
向前<、向後>、關閉X 按鈕實現 (不需要的可以跳過)
當`webView`可以`goForward`時顯示`向前`按鈕;
當`webView`可以`goBack`時顯示`向後`按鈕;
lazy var forwardBtnItem: UIBarButtonItem = {
let btn = UIBarButtonItem(image: UIImage(named: "mo_green_forward"), style: .done, target: self, action: #selector(goForward))
btn.imageInsets = UIEdgeInsets(top: 0, left: -30, bottom: 0, right: 30)
return btn
}()
private lazy var closeBtnItem: UIBarButtonItem = {
let btn = UIBarButtonItem(image: UIImage(named: "mo_green_close"), style: .done, target: self, action: #selector(close))
btn.imageInsets = UIEdgeInsets(top: 0, left: -34, bottom: 0, right: 34)
return btn
}()
// viewDidLoad方法裏:
navigationItem.leftBarButtonItem = backBtnItem
// WKNavigationDelegate 的代理方法
// MARK: - 加載完成
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("加載完成: didFinish")
self.title = webView.title
var btns = [backBtnItem]
if webView.canGoForward {
btns.append(forwardBtnItem)
}
if webView.canGoBack {
btns.append(closeBtnItem)
}
navigationItem.leftBarButtonItems = btns
}
最後是load
let request = URLRequest(url: self.url)
webView.load(request)
// 不能用 webView.loadHTMLString(self.url.absoluteString, baseURL: self.url)
// 否則evaluateJavaScript方法報錯
2、WKNavigationDelegate:
這個代理在加載的各個過程中都有回調,可以根據項目需求,做響應的處理:
前面三個 `func webView(_ webView: WKWebView, decidePolicyFor ...` 的方法是一樣的,只是帶的參數不一樣,如果實現了,就必須調用`decisionHandler`進行響應的處理,否則報錯。
extension MOWKWebViewController: WKNavigationDelegate {
// MARK: - 判斷連接是否允許跳轉 navigationAction
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
print("判斷連接是否允許跳轉: decidePolicyFor navigationAction: \(navigationAction)")
decisionHandler(.allow) // .allow or .calcel
}
// MARK: - 判斷連接是否允許跳轉 navigationAction preferences:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
print("判斷連接是否允許跳轉: decidePolicyFor navigationAction: \(navigationAction) \n preferences: \(preferences)")
decisionHandler(.allow, preferences) // .allow or .calcel
}
// MARK: - 判斷連接是否允許跳轉 navigationResponse
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
print("拿到響應後決定是否跳轉: decidePolicyFor navigationResponse: \(navigationResponse)")
decisionHandler(.allow) // .allow or .calcel
}
// MARK: - 服務器重定向
func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
print("服務器重定向: didReceiveServerRedirectForProvisionalNavigation")
}
// MARK: - 加載完成
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("加載完成: didFinish")
self.title = webView.title
}
// MARK: - 加載失敗
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("加載失敗: didFail error: \(error)")
}
// MARK: - 即將完成
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
print("即將完成: didCommit")
}
// MARK: - 加載錯誤
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
print("加載錯誤: didFailProvisionalNavigation: \(error)")
}
// MARK: - 需要響應身份驗證時調用(需驗證服務器證書)
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
print("需驗證服務器證書: didReceive challenge")
}
// MARK: - web內容進程被終止時調用(iOS 9.0之後)
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
print("進程被終止: webViewWebContentProcessDidTerminate")
}
}
3、WKUIDelegate:
一些UI展示和交互需要App這邊來做,交互的結果卻得反饋給JS:
extension MOWKWebViewController: WKUIDelegate {
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
print("alert: msg:\(message)")
let alertVC = UIAlertController(title: "提示", message:message, preferredStyle: .alert)
alertVC.addAction(UIAlertAction(title: "確認", style: .default, handler: { (action) in
completionHandler() // 告知JS結果
}))
self.present(alertVC, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
print("confirm: msg:\(message)")
let alertVC = UIAlertController(title: "提示", message: message, preferredStyle: .alert)
alertVC.addAction(UIAlertAction(title: "取消", style: .cancel, handler: { (alertAction) in
completionHandler(false) // 告知JS選擇結果
}))
alertVC.addAction(UIAlertAction(title: "確定", style: .default, handler: { (alertAction) in
completionHandler(true) // 告知JS選擇結果
}))
self.present(alertVC, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
print("input: prompt:\(prompt) defaultText:\(String(describing: defaultText))")
let alertVC = UIAlertController(title: prompt, message: "", preferredStyle: .alert)
alertVC.addTextField { (textField) in
textField.text = defaultText
}
alertVC.addAction(UIAlertAction(title: "完成", style: .default, handler: { (alertAction) in
completionHandler(alertVC.textFields![0].text) // 告知JS結果
}))
self.present(alertVC, animated: true, completion: nil)
}
}
4、WKScriptMessageHandler:
接收JS那邊發送過來得消息,消息有name ,是在我們初始化( configuration.userContentController.add(self, name: "moxiaoyan") )設置的。可以根據name來分別處理不同類型的消息。
extension MOWKWebViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
// JS:window.webkit.messageHandlers.moxiaoyan.postMessage
print("didReceive message: \(message.body)")
switch message.name {
case "moxiaoyan":
print("收到發給我的消息啦: \(message.body)")
default: break
}
}
}
這邊貼一下JS那邊的實現
<script type="text/javascript">
// 向APP傳遞數據
function buttonAction() {
try {
window.webkit.messageHandlers.moxiaoyan.postMessage("JS: 客戶下單啦~");
} catch (e) {
console.log(e);
}
}
</script>
二、與JS的交互
其實上面的代理實現的已經差不多了,下面補充並說明一下:
1、evaluateJavaScript的使用
1)、首先我們在`WKNavigationDelegate`的`didFinish`方法回調後,就可以使用`evaluateJavaScript`跟JS交互了:
// MARK: - 加載完成
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("加載完成: didFinish")
// 調用js方法(把標題h1設置成紅色)
webView.evaluateJavaScript("changeHead()", completionHandler: { (data, error) in
print("changeHead data:\(String(describing: data)) error: \(String(describing: error))")
})
}
我在這卡了很久,一直報錯:
Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=1, WKJavaScriptExceptionMessage=ReferenceError: Can't find variable: changeHead, WKJavaScriptExceptionColumnNumber=11,...
是因爲加載網頁的方法用的不對:
我用了: webView.loadHTMLString(_ string: String, baseURL: URL?) -> WKNavigation?
應該用: webView.load(_ request: URLRequest) -> WKNavigation?
2)、這裏在app的原生頁面寫了兩個button調用JS的方法/獲取信息
func setupButtons() {
let baseHeight = (navigationController?.navigationBar.frame.maxY ?? 0) + 20;
let getJSBtn = UIButton(type: .custom)
getJSBtn.setTitle("獲取JS信息", for: .normal)
getJSBtn.setTitleColor(.red, for: .normal)
getJSBtn.addTarget(self, action: #selector(getJSInformation), for: .touchUpInside)
getJSBtn.frame = CGRect(x: 30, y: view.frame.size.height/2 + baseHeight, width: 120, height: 40)
view.addSubview(getJSBtn)
let callJSFuncBtn = UIButton(type: .custom)
callJSFuncBtn.setTitle("調用JS方法", for: .normal)
callJSFuncBtn.setTitleColor(.red, for: .normal)
callJSFuncBtn.addTarget(self, action: #selector(callJSFunc), for: .touchUpInside)
callJSFuncBtn.frame = CGRect(x: 200, y: view.frame.size.height/2 + baseHeight, width: 120, height: 40)
view.addSubview(callJSFuncBtn)
}
// MARK: - 獲取JS title,並打印
@objc func getJSInformation() {
let js = "document.getElementsByTagName('h1')[0].innerText";
webView.evaluateJavaScript(js, completionHandler: { (data, error) in
print("getJSInformation data:\(String(describing: data)) error: \(String(describing: error))")
})
}
// MARK: - 調用JS的方法,並打印返回數據
@objc func callJSFunc() {
webView.evaluateJavaScript("swiftTestObject('xjf', 26)", completionHandler: { (data, error) in
print("callJSFunc data:\(String(describing: data)) error: \(String(describing: error))")
})
}
下面是HTML文件的代碼:
<html lang="en">
<head>
<meta charset="utf-8" />
<title>JS交互</title>
<style>
body {
font-size:30px;
text-align:center;
}
* {
margin: 30px;
padding: 0;
}
h1{
<!-- color: red;-->
}
button {
width: 300px;
height: 50px;
font-size: 30px;
}
</style>
</head>
<body>
<h1>WKWebview與iOS交互</h1>
<h2></h2>
<button onclick="testA()">點擊alert APP彈框</button>
<button onclick="testConfrim()">點擊Confrim APP彈窗</button>
<button onclick="testPrompt('你是誰???')">點擊Prompt APP彈窗</button>
<button onclick="buttonAction()">向APP傳遞數據</button>
<script type="text/javascript">
// APP調用js
function changeHead(){
document.querySelector('h1').style.color = "red"
}
// 接受APP傳過來的參數
function swiftTestObject(name, age) {
var object = {name:name, age:age};
return object;
}
function testConfrim() {
confirm("確定修改數據嗎?")
}
function testPrompt(title) {
prompt(title, "莫小言")
}
// 無參數函數
function testA() {
alert("我是JS中的彈窗消息");
}
// 向APP傳遞數據
function buttonAction() {
try {
window.webkit.messageHandlers.moxiaoyan.postMessage("JS: 客戶下單啦~");
} catch (e) {
console.log(e);
}
}
</script>
</body>
</html>
還有些代理方法沒有實現,沒有研究,待續吧~