SwiftUIの `@Environment(\.openURL)` を使って URLをアプリ内ブラウザで開く

SwiftUIの `@Environment(\.openURL)` を使って URLをアプリ内ブラウザで開く

Clock Icon2025.04.20

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をアプリ内ブラウザで開く
  • ButtonLinkなどのSwiftUI標準コンポーネントでURLを扱う際にシームレスに動作させる

サンプルプログラムを実行すると、以下の動画のようにURLをアプリ内ブラウザで開くことができる。

IMB_3me1MH

実装手順

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)
            }
    }
}

このモディファイアでは、以下の処理を実装している。

  1. environment(\.openURL, ...)を使用して、URLを開く際の動作をカスタマイズ
  2. 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 へアップロードしておいた。

https://gist.github.com/CH3COOH/d038de1989906622a98e285d4e1231cf

まとめ

SwiftUIのEnvironment Valueを活用することで、アプリのURL処理を簡単にカスタマイズできることがわかった。

この手法を使うことで、ユーザーはアプリから出ることなくWebコンテンツを閲覧できるようになり、SwiftUIの標準コンポーネントとの互換性も維持できる。また、コードの再利用性と保守性が向上し、URLスキームに基づいた柔軟な処理が可能になる。OpenURLActionSFSafariViewControllerを組み合わせたこのパターンは、特にコンテンツの閲覧や情報提供が主要な機能であるアプリにおいて、実用的で効果的なアプローチとなるだろう。

参考

https://developer.apple.com/documentation/swiftui/openurlaction

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.