Skip to main content
  1. Posts/

WKWebViewのカスタムUserAgentを設定する方法

UIWebViewがiOS13から非推奨になり、WKWebViewへの移行が進んでいます。 しかし、移行は一筋縄では行かないことが多いです。私の場合は、User Agentの設定で、かなり苦戦しました。 そこで今回は、WKWebViewにUser Agentを設定する方法をまとめます。

WKWebViewのUserAgent設定方法 #

それでは、WKWebViewでの設定方法をまとめていきます。いくつかあります。

  • UserDefaultsにセットする
  • WKWebViewのカスタムUAにセットする
  • デフォルトのUAの後ろにカスタム文字列を付ける方法
  • Interface Builderで設定する

UserDefaultsにセットする #

これはUIWebView時代と変わりません。しかし、WKWebViewのインスタンスが生成される前にセットする必要があります。

var webView: WKWebView!

override func loadView() {
    super.loadView()

    // WKWebViewのインスタンスを作る前にUserAgentをセットする
    setUserAgent()

    webView = WKWebView()
    webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    self.view = webView

    webView.navigationDelegate = self
}

override func viewDidLoad() {
    super.viewDidLoad()

    load()
}

private func setUserAgent() {
    UserDefaults.standard.register(defaults: ["UserAgent": "CustomUserAgent"])
}

インスタンス生成前であればいつでも良いので、AppDelegateで実行するのも良いでしょう(許容されるならば)。

WKWebViewのカスタムUAにセットする #

UserDefaultsよりもかんたんなのが、WKWebViewのcustomUserAgentに文字列をセットする方法です。

webView.customUserAgent = "CustomUserAgent"

これであれば、UserDefaultsを使わないだけでなく、インスタンスごとに個別のUserAgentを持つことができます。 複数のWebViewを使う要件であれば、こちらを使うのが便利でしょう。

アプリ名をsuffixとして付与する場合 #

WKWebViewConfigurationを使用します。applicationNameForUserAgentというプロパティにセットした文字列が、suffixとして付与されます。 UIWebViewとは異なる方法です。

var webView: WKWebView!

override func loadView() {
    super.loadView()

    // configにカスタムで付与したい文字列をセット
    let config = WKWebViewConfiguration()
    config.applicationNameForUserAgent = "CustomUserAgent"

    // WKWebViewのインスタンス生成時に、configを渡す
    webView = WKWebView(frame: .zero, configuration: config)
    webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    self.view = webView

    webView.navigationDelegate = self
}

実際に、WKNavigationDelegatewebView(_:decidePolicyFor:decisionHandler:)で、リクエストヘッダを見てみます。

Optional(["User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CustomUserAgent", ...])

確かに、カスタムのUserAgent文字列が、後ろに付与されていることがわかります。 このように、WKWebViewConfigurationを使うことで、suffixをつけられるようになりました。 WKWebViewは、storyboard・Interface Builderではなく、コードからインスタンスを生成するほうが、何かと都合がいいですね……。

Interface BuilderでカスタムUAをつけられるようになっていた! #

Interface Builderで設定可能

上記画像の通り、Interface BuilderのInspectorで、User AgentとApp Name(applicationNameForUserAgent)が設定できるようになっていました。上記の画像のように設定した場合、Xcode 11.3.1のシミュレーターでは、以下のようなUAになりました。

Mozilla/5.0 (iPhone; CPU iPhone OS 13_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) UISamplesIOS

これで、Interface Builderで定義したレイアウトを大きく変更せずとも、WKWebViewへ移行できますね!

JavaScriptの実行結果ではだめなのか #

WKWebViewにもJavaScriptの実行結果を取得できるメソッドが用意されていますが、その仕様はUIWebViewとは変わりました。 クロージャで返ってくるようになり、実行結果は非同期で取得することになります。

// 使用するAPI
func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil)
var webView: WKWebView? = WKWebView()
webView?.evaluateJavaScript("navigator.userAgent") { (userAgent, error) in
    guard let originalUserAgent = userAgent as? String else { return }
    
    // クロージャが実行されるまで、webViewインスタンスが必要なのでキャプチャしておく
    webView = nil
    
    // originalUserAgentを処理するなり、保存するなり
    let customizedUA = originalUserAgent + "CustomUserAgent"
    UserDefaults.standard.register(defaults: ["UserAgent" : customizedUA])
}

UserAgentを取得・セットするタイミングが不定でも問題ないのであれば、この方法でも大丈夫だと思います。 しかし、UIWebViewの実装のように順次実行されるわけではないので、UAが設定されるタイミングを制御できません。 UA設定のタイミングが重要な場合は、WKWebViewConfigurationを使うほうが安全です。

非同期実行が困るという場合、同期的に実行したい場合 #

アプリの都合から、非同期では困る場合もあると思います。その場合は、処理の待ち合わせを手軽に実装できる、DispatchSemaphoreを使用すると良いでしょう。DispatchSemaphoreについての詳しい記事は、以下になります。

非同期を同期的に待ち合わせる方法(ライブラリは使わず・Swift)


まとめ #

WKWebViewでUserAgentをカスタマイズする方法をまとめました。 UIWebViewと似ているようで、実際には全く異なるAPIなので、移行には苦戦することもあります。 一方で、WKWebViewConfigurationを使うことによりsuffixをつけやすくなったり、WKNavigationDelegate・WKUIDelegateを使って細かく挙動を制御できたりと、柔軟性も増しています。 ネイティブアプリではWebViewを酷使することは少ないですが、もしUserAgentを変更してWebViewを使用する際には、参考にしてもらえると幸いです。


おまけの振り返り: UIWebViewの頃 #

いまいちどUIWebView時代は、どのようにしてUser Agentを設定していたかを振り返ります。

UserDefaultsにセットしていた #

UIWebViewは、UserDefaultsに保存してあるUser Agentの値を使用します。 UserDefaultsに、UserAgentのキーでStringが保存されていれば、その値を使用します。 実装の仕方は、このような感じ。

var webView: UIWebView!

override func viewDidLoad() {
    super.viewDidLoad()

    setUserAgent()
    load()
}

/// UserAgentを設定する
private func setUserAgent() {
    UserDefaults.standard.register(defaults: ["UserAgent": "CustomUserAgent"])
}

/// ページを読み込む
private func load() {
    let request = URLRequest(url: URL(string: "https://google.com/")!)
    webView.loadRequest(request)
}

では、UserAgentの変化を見てみます。 UIWebViewDelegatewebView(_:shouldStartLoadWith:navigationType:)でURLRequestが取れるので、allHTTPHeaderFieldsをprintしてみます。

func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebView.NavigationType) -> Bool {
    print(request.allHTTPHeaderFields)
    return true
}
// カスタム値をセットする前のUserAgent
Optional(["User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 12_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", ...])

// セットしたあとのUserAgent
Optional(["User-Agent": "CustomUserAgent", ...])

たしかにUserAgentが変更されたことがわかりました。

アプリ名をsuffixとして付与する場合 #

「UserAgentを全部変えたいわけじゃない、デフォルトUAの後ろにアプリ名をつけたいんだ」という要件は多いようです。UIWebViewでは、こうやっていました。

let originalUA = UIWebView().stringByEvaluatingJavaScript(from: "navigator.userAgent")!
let customizedUA = originalUA + " CustomUserAgent"
UserDefaults.standard.register(defaults: ["UserAgent": customizedUA])

JavaScriptの実行結果がStringで返ってくるので、その後ろに、任意の文字列をくっつけるだけで完成でした。


ここまで読んでくださってありがとうございます。 それでは失礼いたします。