[iOS]使用WKWebView遇到的問題總結

1. 獲取UserAgent為nil

在使用WKWebView獲取userAgent的時候, 如果要全局配置, 使所有的WKWebView都能生效, 我們可能的做法是在AppDelegate中來配置, 但是需要一個WKWebView實例對象, 所以可能是這么寫的:

let webView = WKWebView()
webView.evaluateJavaScript("navigator.userAgent") { (info, error) in
            // 獲取默認值
            if var userAgent = info as? String {
                // 添加自定義的內容
                if userAgent.hasSuffix("/ios-app") == false {
                    userAgent += "/ios-app"
                }
                // 設置global User-Agent
                let dic = ["UserAgent": userAgent]
                UserDefaults.standard.register(defaults: dic)
                UserDefaults.standard.synchronize()
            }
        }

這樣, 你會發(fā)現設置一直是無效的, 在回調里打印一下:

print(info)
print(error)

這時發(fā)現獲取到的 info 字段為nil, 并且error信息如下:

Error Domain=WKErrorDomain Code=3 "WKWebView 已失效" UserInfo={NSLocalizedDescription=WKWebView 已失效}

個人猜測: 這是因為, WKWebView的evaluateJavaScript方法是異步執(zhí)行的, 當WKWebView回調這個方法的時候, 其實例對象已經從內存中釋放了, 所以導致回調出錯.
我做了如下驗證, 在回調方法里輸出webView實例對象:

let webView = WKWebView()
webView.evaluateJavaScript("navigator.userAgent") { (info, error) in

          print(info)
          print(error)
          print(webView)
            // 獲取默認值
            if var userAgent = info as? String {
                // 添加自定義的內容
                if userAgent.hasSuffix("/ios-app") == false {
                    userAgent += "/ios-app"
                }
                // 設置global User-Agent
                let dic = ["UserAgent": userAgent]
                UserDefaults.standard.register(defaults: dic)
                UserDefaults.standard.synchronize()
            }
        }

會發(fā)現輸出是info有值, 而error為nil, webView有值, 又正常了, 有人說了,看樣子不是這個問題! 真的么? 仔細想一下會發(fā)現, 在webView的方法回調閉包里使用了webView實例, 會發(fā)生什么? 對, 循環(huán)引用! webView實例此時不為nil, 這也驗證了, 如果webView實例正常的話, 獲取結果是不會有誤的! 繼續(xù)上面的驗證, 我們弱引用一下:

let webView = WKWebView()
webView.evaluateJavaScript("navigator.userAgent") {[weak webView] (info, error) in

          print(info)
          print(error)
          print(webView)
            // 獲取默認值
            if var userAgent = info as? String {
                // 添加自定義的內容
                if userAgent.hasSuffix("/ios-app") == false {
                    userAgent += "/ios-app"
                }
                // 設置global User-Agent
                let dic = ["UserAgent": userAgent]
                UserDefaults.standard.register(defaults: dic)
                UserDefaults.standard.synchronize()
            }
        }

這時, 會發(fā)現: info和webView都為nil, error值為上面那個錯誤!!!

這樣, 基本驗證出現這個問題的原因是: webView 提前釋放了!

但是為了添加這個設置, 而將webView 設為全局變量, 仿佛有點得不償失, 這時可以在使用webView的頁面進行設置, 或者使用UIWebView替換:

// 獲取默認值
        if let oldAgent = UIWebView().stringByEvaluatingJavaScript(from: "navigator.userAgent") {
            var newAgent = oldAgent

            // 添加自定義的內容
            if oldAgent.hasSuffix("/artron-cgyc") == false {
                newAgent += "/artron-cgyc"
            }

            // 設置global User-Agent
            let dic = ["UserAgent": newAgent]
            UserDefaults.standard.register(defaults: dic)
            UserDefaults.standard.synchronize()
        }
2. 無需傳參時注入的交互協(xié)議無效

在做JS與原生交互的時候, 使用下面方法注入的協(xié)議無效:

 let user = WKUserContentController()
 // 向js中注入協(xié)議, 作為ios和js交互的依據
 user.add(self, name: "appProtocol")

然后在js端使用的時候: 這里不需要傳參數, 直接這么寫的

window.webkit.messageHandlers.appProtocol.postMessage();

這樣, 沒有響應js端的事件!!!
在代理方法中:

func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        
        if message.name == "appProtocol" {
            print(message.body)
        }
    }

一直沒有收到回調!!!
其實, 并不是注入協(xié)議失敗, 這么使用也沒問題, 問題就出在postMessage的參數上, 如果是帶參數的:

window.webkit.messageHandlers.appProtocol.postMessage({info: 'info', num: '123456788', price: '100'});

這么寫, 是完全沒有問題的, 所以如果不需傳參數的話, 可以這么寫:

// 無參數使用
window.webkit.messageHandlers.appProtocol.postMessage({});

給一個空的字典, 就能正常交互了!!!

3. 屏蔽頁面長按手勢

在WKWebView加載的HTML頁面上, 如果長按會彈出一些選擇框, 在文字上長按, 會彈出UIMenuController選擇框:

長按文字

而在圖片上長按, 會彈出一個alertSheet:

屏幕快照 2017-11-30 下午3.20.31.png

這里可以保存圖片到系統(tǒng)相冊(如果有權限), 或者復制到剪切板. 但是這些需求并不是我們需要, 如何禁止這些行為呢?需要從JS入手, 只需要執(zhí)行下面兩句js即可:

// 禁止圖片長按事件
document.documentElement.style.webkitTouchCallout='none';
//禁止文本長按事件
document.documentElement.style.webkitUserSelect='none';

可以在創(chuàng)建WKWebView的時候注入:

let config = WKWebViewConfiguration()
config.userContentController = WKUserContentController()
let jsStr = """
    document.documentElement.style.webkitTouchCallout='none';
    document.documentElement.style.webkitUserSelect='none';
          """
let noneSelectJS = WKUserScript(source: jsStr, injectionTime: WKUserScriptInjectionTime.atDocumentEnd, forMainFrameOnly: true)
config.userContentController.addUserScript(noneSelectJS)
let web = WKWebView(frame: .zero, configuration: config)

也可以在頁面加載完成后的代理方法中執(zhí)行:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {

        // 結束加載
        let jsStr = """
                    document.documentElement.style.webkitTouchCallout='none';
                    document.documentElement.style.webkitUserSelect='none';
                """
        webView.evaluateJavaScript(jsStr) { (info, error) in
            
        }
    }
4. 頁面中出現第三方的廣告懸浮框(Ta)

在加載的HTML頁面中, 無端出現一個廣告的懸浮框:

第三方廣告懸浮框

打開之后是這樣的:

第三方廣告頁面

而且只會在移動4G網絡下才會出現, 其實這是移動的流量劫持, 強加的廣告推廣叉寂,目前網上有一些解決方式蹋绽,常用的有:

  1. 使用IP地址訪問:將域名改為IP地址訪問數據;
  2. 使用HTTPS愧薛;
  3. 前端的小伙伴想想辦法

其他的可參考這篇文章 iOS 客戶端對于運營商劫持的一點點對抗方式
兵钮。

5. 顯示HTML頁面不是最新的內容

在聯調的時候, 前端的同學改了一些東西, 例如頁面的布局, 顯示元素, 或者js方法, 而APP端沒反應!!!

這是因為, WKWebView有緩存, 為了保證每次加載的都是最新的頁面, 可以在加載的鏈接后面加上一個時間戳, 例如你的HTML地址為:

http://your host name/test/20171127.html

一般使用是這樣的:

let urlStr = "http://your host name/test/20171127.html"

      if let url = URL(string: urlStr) {
            let request = URLRequest(url: url)
            webView.load(request)
}

這樣的話是有緩存, 加載一次之后, 再去加載也不是最新的頁面, 可以這樣使用:

let time = Date().timeIntervalSince1970   
let urlStr = "http://your host name/test/20171127.html?_t=\(time)"
                
        if let url = URL(string: urlStr) {
            let request = URLRequest(url: url)
            webView.load(request)
        }

這樣每次加載的時候都會是最新的, 當然弊端就是, 每次都會耗費一些額外的流量.

6. 當前頁面無導航時不能填充狀態(tài)欄(iOS11+ 會下移狀態(tài)欄的高度)

在頁面無導航的情況下耐版,系統(tǒng)會自動調節(jié)滾動視圖的contentInset全景,使其視圖永遠處于狀態(tài)欄之下,但是如果我們想讓滾動視圖的Y坐標從屏幕頂端(狀態(tài)欄)開始朋贬,我們都知道怎么修改凯楔,但是 WKWebView不是繼承自UIScrollView 的,所以不能直接設置锦募,可以這么寫:

if (@available(iOS 11.0, *)) {
        self.webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    } else {
        // Fallback on earlier versions
        self.automaticallyAdjustsScrollViewInsets = NO;
    }
7. Attempt to add script message handler with name 'action_toiOS' when one already exists.

這個閃退發(fā)生在與 JS 進行交互摆屯,使用下面的方法注冊協(xié)議時:

 let user = WKUserContentController()
 // 向js中注入協(xié)議, 作為ios和js交互的依據
 user.add(self, name: "action_toiOS")

如果重復注冊了相同名稱的協(xié)議,就會發(fā)生閃退糠亩,所以在使用完webView的時候虐骑,一定要記得移除已注冊的協(xié)議:

self.wkWeb.configuration.userContentController.removeScriptMessageHandler(forName: "action_toiOS")
8. Invalid parameter not satisfying: targetNod

app首頁使用 WKWebView 來承載的內容,在啟動時赎线,如果添加了引導頁/廣告頁廷没,這時如果有手勢操作,例如在出現廣告頁時點擊屏幕垂寥,就會閃退颠黎,并在控制臺輸出:


Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: sourceNode'
*** First throw call stack:
(0x1d7131ea4 0x1d6301a50 0x1d7047a2c 0x1d7b361d0 0x203ed4984 0x203ed34e4 0x203edb728 0x203ee144c 0x203edd398 0x203edd2cc 0x203edd09c 0x204308cb4 0x2042e7fcc 0x2043b6e38 0x2043b9830 0x2043b2320 0x1d70c20e0 0x1d70c2060 0x1d70c1944 0x1d70bc810 0x1d70bc0e0 0x1d9335584 0x2042ccc00 0x1042c47a4 0x1d6b7abb4)

添加一個全局斷點,調試發(fā)現崩潰信息為

 Assertion failure in -[UIGestureGraphEdge initWithLabel:sourceNode:targetNode:directed:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKitCore/UIKit-3698.94.14/Source/GestureGraph/UIGestureGraphEdge.m:24

查了些資料滞项,沒找到具體原因狭归,但是了解到和 +load方法有關,我是在 +load 方法內初始化的廣告頁的信息文判,把這些放在 didFinishLaunchingWithOptions 進行初始化过椎,就不會有這個問題;

解決:
將廣告/引導頁視圖的初始化放在 didFinishLaunchingWithOptions 方法內戏仓。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末疚宇,一起剝皮案震驚了整個濱河市亡鼠,隨后出現的幾起案子,更是在濱河造成了極大的恐慌敷待,老刑警劉巖间涵,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異讼撒,居然都是意外死亡浑厚,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門根盒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人物蝙,你說我怎么就攤上這事炎滞。” “怎么了诬乞?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵册赛,是天一觀的道長。 經常有香客問我震嫉,道長森瘪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任票堵,我火速辦了婚禮扼睬,結果婚禮上,老公的妹妹穿的比我還像新娘悴势。我一直安慰自己窗宇,他們只是感情好,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布特纤。 她就那樣靜靜地躺著军俊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捧存。 梳的紋絲不亂的頭發(fā)上粪躬,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音昔穴,去河邊找鬼镰官。 笑死,一個胖子當著我的面吹牛傻咖,可吹牛的內容都是我干的朋魔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼卿操,長吁一口氣:“原來是場噩夢啊……” “哼警检!你這毒婦竟也來了孙援?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤扇雕,失蹤者是張志新(化名)和其女友劉穎拓售,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體镶奉,經...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡础淤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了哨苛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸽凶。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖建峭,靈堂內的尸體忽然破棺而出玻侥,到底是詐尸還是另有隱情,我是刑警寧澤亿蒸,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布凑兰,位于F島的核電站,受9級特大地震影響边锁,放射性物質發(fā)生泄漏姑食。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一茅坛、第九天 我趴在偏房一處隱蔽的房頂上張望音半。 院中可真熱鬧,春花似錦灰蛙、人聲如沸祟剔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽物延。三九已至,卻和暖如春仅父,著一層夾襖步出監(jiān)牢的瞬間叛薯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工笙纤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耗溜,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓省容,卻偏偏與公主長得像抖拴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內容