今回はSFSafariViewControllerを使用したInAppブラウザ表示の実装例を書いていこうと思います。
View側(NavigationLinkと組み合わせて使用しない方が良さそう)
ここでわざわざタイトルに書いているのは、NavigationLinkとSFSafariViewControllerの相性が悪かった為です。最初の実装ではNavigationLinkのdestinationにSafariViewを設定して表示させようとしたのですが、そうするとヘッダーの完了ボタンが効かなくなりブラウザを閉じることができなくなるなどいくつかの不具合に遭遇しました。なのでfullScreenCoverを使用して実装する方針へ切り替えることにしました。参考コードは下記のようになります。
@State var isSafariWebViewShow: Bool = false
Button {
isSafariWebViewShow = true
} label: {
Text("詳しく見る")
}
.fullScreenCover(isPresented: $isSafariWebViewShow, content: {
SafariWebView(url: URL(string: "\(url)")!, onClose: {
// 非表示後に何かやりたい処理があればここに書く
})
})
SafariWebViewの詳細実装
import SafariServices
import SwiftUI
struct SafariWebView: UIViewControllerRepresentable {
var url: URL
var onClose: () -> Void
func makeUIViewController(context: Context) -> SFSafariViewController {
let safariViewController = SFSafariViewController(url: url)
safariViewController.delegate = context.coordinator
return safariViewController
}
func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, SFSafariViewControllerDelegate {
var parent: SafariWebView
init(_ safariWebView: SafariWebView) {
self.parent = safariWebView
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
parent.onClose()
}
}
}
実装のポイント
SFSafariViewControllerはUIViewControllerを継承しているクラスなので、SwiftUIで使用する際にはUIViewControllerRepresentableを使用する必要があります。書き方についてはどこにでもあるテンプレートパターンと同じです。今回の場合はSafariWebViewが閉じた後に特定のイベントを発火させる必要がありました。SFSafariViewControllerのDelegateには終了を検知するイベントが用意されているので、それを使用する為にCoordinatorクラスを定義しています。特に不要であればこのあたりも省略して書くことができます。
終わりに
以上がSFSafariViewControllerを使用したInAppブラウザでの表示方法となります。UIViewControllerRepresentableの扱いに慣れていれば、そこまでSafariWebView側の実装は難しくなく、むしろView側での表示方法がSwiftUI側のViewコンポーネントとの相性の兼ね合いもあり時間が掛かった印象です。