
SwiftUIの `@Environment(\.openURL)` を使って URLをアプリ内ブラウザで開く
SwiftUIアプリで@Environment(\.openURL)
を使って外部リンクを扱う際、デフォルトではSafariアプリが起動してしまう。ユーザー体験を向上させるためには、アプリ内ブラウザでリンクを開きたいことが多い。きんくま氏の「[SwiftUI] iOS 17以降の@Environmentまとめ」にて、OpenURLAction
の挙動をカスタマイズできることを知った。
本記事では、SwiftUIのEnvironment Valueを活用して、URLをアプリ内ブラウザ(SFSafariViewController)で表示する方法を解説する。
検証環境
- Xcode 16.2
- iPhone 16 Pro / iOS 18.3 (シミュレータ)
実現したいこと
このサンプルコードでは、以下の機能を実装する:
- SwiftUIの
openURL
のEnvironment Valueをカスタマイズして、URLをアプリ内ブラウザで開く Button
やLink
などのSwiftUI標準コンポーネントでURLを扱う際にシームレスに動作させる
サンプルプログラムを実行すると、以下の動画のようにURLをアプリ内ブラウザで開くことができる。
実装手順
1. SafariViewの実装
まずは、UIKitのSFSafariViewControllerをSwiftUIで利用できるようにラッパーコンポーネントを作成する。UIViewControllerRepresentable
プロトコルを採用することで、SwiftUIからUIKitのコンポーネントを簡単に利用できる。
private struct SafariView: UIViewControllerRepresentable {
let url: URL
func makeUIViewController(context _: Context) -> SFSafariViewController {
let vc = SFSafariViewController(url: url)
vc.dismissButtonStyle = .close
vc.preferredControlTintColor = .systemPink
return vc
}
func updateUIViewController(_: SFSafariViewController, context _: Context) {
// SFSafariViewControllerは内部状態を管理するため、更新は不要
}
}
2. IdentifiableURL構造体の作成
SwiftUIのsheet(item:)
モディファイアを使用するには、表示対象のアイテムがIdentifiable
プロトコルに準拠している必要がある。そのため、URLをラップする構造体を作成する。
/// URL をシート表示用に Identifiable に適合させるためのラッパー構造体
/// - Note: sheet(item:) で使用するために必要
struct IdentifiableURL: Identifiable {
let id = UUID()
let url: URL
init(_ url: URL) {
self.url = url
}
}
3. SafariSheetModifierの実装
次に、 @Environment(\.openURL)
のカスタマイズ処理とSafariViewをシート表示するロジックを組み合わせたSafariSheetModifier
を実装する。
struct SafariSheetModifier: ViewModifier {
@State private var identifiableURL: IdentifiableURL?
func body(content: Content) -> some View {
content
.environment(\.openURL, OpenURLAction { url in
// HTTP/HTTPS スキーム以外は標準の動作(ブラウザで開く等)にフォールバック
if url.scheme == "https" || url.scheme == "http" {
identifiableURL = IdentifiableURL(url)
return .handled
} else {
return .systemAction
}
})
.sheet(item: $identifiableURL) { identifiableURL in
SafariView(url: identifiableURL.url)
}
}
}
このモディファイアでは、以下の処理を実装している。
environment(\.openURL, ...)
を使用して、URLを開く際の動作をカスタマイズsheet(item:)
を使用して、HTTPSまたはHTTPスキームのURLが開かれたときにSafariViewをシートとして表示する
URLのスキームを確認し、HTTPまたはHTTPSの場合は内部でシート表示し、それ以外のスキーム(tel:、mailto:など)はシステムのデフォルト動作に委ねる。
4. View拡張の実装
モディファイアを簡単に適用できるようにViewのextensionを作成する。
extension View {
/// View に Safari シート表示機能を追加する
/// アプリ内で開かれる URL を SFSafariViewController で表示するようにする
func safariSheet() -> some View {
modifier(SafariSheetModifier())
}
}
このextensionを利用することで、任意のViewに.safariSheet()
を適用するだけで、そのView階層内でのURL処理をカスタマイズできるようになる。
5. 実際の使用例
実際のアプリでの使用例を見てみよう。アプリレベルで適用することで、アプリ全体でURLを一貫して処理できる。
@main
struct SampleOpenUrlApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.safariSheet() // アプリ全体に Safari シート機能を適用
}
}
}
そして、コンテンツビューではEnvironment ValueからopenURL
アクションを取得し、それを使ってURLを開く。
struct ContentView: View {
@Environment(\.openURL) private var openURL
private static let url = URL(string: "https://dev.classmethod.jp/author/wada-kenji/")!
var body: some View {
VStack(spacing: 24) {
Button(action: openSampleURL) {
VStack {
Text("Open Action")
}
}
Link("Open Link", destination: ContentView.url)
}
}
private func openSampleURL() {
openURL(ContentView.url)
}
}
この実装により、ボタンをタップするかLink
コンポーネントを選択すると、Safariアプリを起動せず、SFSafariViewControllerがアプリ内でシート表示される。
サンプルコード
今回使用したサンプルコードをすべて gist へアップロードしておいた。
まとめ
SwiftUIのEnvironment Valueを活用することで、アプリのURL処理を簡単にカスタマイズできることがわかった。
この手法を使うことで、ユーザーはアプリから出ることなくWebコンテンツを閲覧できるようになり、SwiftUIの標準コンポーネントとの互換性も維持できる。また、コードの再利用性と保守性が向上し、URLスキームに基づいた柔軟な処理が可能になる。OpenURLAction
とSFSafariViewController
を組み合わせたこのパターンは、特にコンテンツの閲覧や情報提供が主要な機能であるアプリにおいて、実用的で効果的なアプローチとなるだろう。
参考