iOSやiPadOSではアプリケーション上でWebページを表示する際には、UIWebViewWKWebViewが使われてきました。 iOS 9からSFSafariViewControllerが登場し、Webページを表示する方法が増えました。

今回は、SFSafariViewControllerを使用した際にリンク先に遷移しないバグにハマったので備忘として記録します。

UIWebViewについて

執筆時点では、UIWebViewの使用は非推奨となっています。 2020年4月からUIWebViewに関するAPIを使用したアプリケーションは審査が通らなくなる旨がアナウンスされました。1

これからの開発でWebViewを使用したい場合は、WKWebViewまたはSFSafariViewControllerに切り替える必要があります。

WKWebViewについて

WKWebViewはiOS 8から使用できるようになったWebViewです。 リクエストの前後にイベントを仕込んだりと各種カスタマイズが可能です。 ページの次へ進む/前に戻るなども自前で実装する必要があります。

また、OS標準のSafariとCookieは共有できませんが、WKWebViewどうしでは共有が可能です。

SFSafariViewControllerについて

SFSafariViewControllerはiOS 9から使用できるようになったSafariの標準的な機能を備えたWebViewです。 非常にシンプルなAPIで構成されており、WKWebViewとは異なり、カスタマイズがほとんどできません。

SFSafariViewControllerDelegateに関しても、

  1. 最初に表示する画面の読み込み完了した時
  2. ビューを閉じた時
  3. アクションボタンをタップし、UIActivityViewControllerに特定のUIActivityを追加
  4. アクションボタンをタップし、UIActivityViewControllerから特定のUIActivityTypesを除外
    • iOS 11から
  5. 最初に表示する画面の読み込み中に別のURLにリダイレクトされた時
    • iOS 11から

の5つが用意されているのみです。

OS標準のSafariとCookieは共有が可能です。 しかし、ユーザーが最後にSafari上でプライベートブラウズモードを使用していた場合、SFSafariViewControllerでもプライベートブラウズモードを使用して対象のURLを表示する点に注意が必要です。 また、iOS 11からはOS標準のSafariとCookieの保存領域が分けられたため、SFAuthenticationSessionという認証情報を共有するためのクラスで実現することになります。

以下はWebページを表示する例です。

// Swift Playgroundで実行
import PlaygroundSupport

import UIKit
import SafariServices

PlaygroundPage.current.liveView = SFSafariViewController(url: NSURL(string: "https://kenchan0130.github.io")! as URL)

SFSafariViewControllerでは新しいタブまたはウィンドウを開くことができない

SFSafariViewControllerを使用した場合、Webページ内に

<a href="https://kenchan0130.github.io" target="_blank">https://kenchan0130.github.io</a>

のようなリンクがあった場合、タップしても該当のリンク先を開くことができません。 この不具合はiOSシミュレーターでは発生せず、実機のみで発生するためハマりました。 一応、触角タッチや3D Touchを行う、または「Safariで開く」ボタンをタップしてOS標準のSafariを使用すれば該当リンクを開くことができますが、このUXをユーザーに強いるのは厳しいものがあります。

これらはiOS 13の環境で検証しました。ほかのバーションでは発生しない、または新しいバーションでは修正されている可能性があります。

問題への対処方法

Webページ側で新しいタブやウィンドウを使わない開き方をしないように変更できればよいですが、実現が難しいことも多々あるでしょう。

前述の通り、SFSafariViewControllerは処理を挿入するなどのカスタマイズが制限されているため、この問題に対応できません。 もしアプリケーション側でどうしても対応したい場合は、以下のようにWKWebViewを使用し、WKUIDelegateで該当のアクションに時に処理を差し込むことで対応可能です。

// Swift Playgroundで実行
import PlaygroundSupport

import UIKit
import WebKit

class ViewController: UIViewController {

    let wkWebView = WKWebView()

    override func viewDidLoad() {
        super.viewDidLoad()

        wkWebView.frame = view.frame
        wkWebView.navigationDelegate = self as? WKNavigationDelegate
        wkWebView.uiDelegate = self
        wkWebView.allowsBackForwardNavigationGestures = true

        let urlRequest = URLRequest(url:URL(string: "https://kenchan0130.github.io")!)
        wkWebView.load(urlRequest)
        view.addSubview(wkWebView)
    }
}

// 新しいタブやウィンドウを開く挙動の対応
extension ViewController: WKUIDelegate {

    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration,
                 for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {

        if navigationAction.targetFrame == nil {
            webView.load(navigationAction.request)
        }

        return nil
    }

}

PlaygroundPage.current.liveView = ViewController()

まとめ

SFSafariViewControllerを使用した際にリンク先に遷移しないバグにハマったので対応方法を紹介しました。 SFSafariViewControllerではどうにもできないので表示するサイトによって、サイト側を変更するかアプリケーション側を変更するかを選択するとよいでしょう。