mcpField
S
swiftui-webkit

swiftui-webkit

github.com/dpearson2699開発1.2k3.3k 回インストール13 週間前に更新
リポジトリ

swiftui-webkit について

SwiftUI WebKitは、iOS 26などのプラットフォームでネイティブWebKit APIを使い、SwiftUIにウェブコンテンツを組み込む開発者向けガイドです。ページ状態、ナビゲーションポリシー、JavaScript連携、アプリ独自のローカルコンテンツを扱います。

主な機能

  1. 01SwiftUIネイティブのWebViewとWebPage
  2. 02ページ読み込みと状態監視
  3. 03ナビゲーションポリシー制御
  4. 04引数付きJavaScript呼び出し
  5. 05カスタムURLスキームでのローカルコンテンツ

活用シーン

  1. 01アプリ内でのウェブコンテンツ表示
  2. 02ページ読み込みとナビゲーション状態の監視
  3. 03外部ドメインへのリンク転送
  4. 04オフラインHTMLドキュメントの表示

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swiftui-webkit

インストール後、Claude Code で/swiftui-webkitを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
evals.json
references/
loading-and-observation.md
local-content-and-custom-schemes.md
migration-and-fallbacks.md
navigation-and-javascript.md

詳細

SwiftUI WebKit

iOS 26、iPadOS 26、macOS 26、visionOS 26 向けに導入されたネイティブの WebKit-for-SwiftUI API を使用して、SwiftUI に Web コンテンツを埋め込み、管理します。アプリに統合された Web 表示領域、アプリが所有する HTML コンテンツ、JavaScript を利用したページ操作、またはカスタムナビゲーションポリシーの制御が必要な場合に、このスキルを使用してください。

目次

適切な Web コンテナーを選ぶ

用途に合った、最も限定的なツールを使用してください。

必要な用途標準の選択肢
SwiftUI でアプリが所有する Web コンテンツを埋め込むWebView + WebPage
iOS/iPadOS で Safari の動作を伴うモーダルブラウジングSFSafariViewController
macOS または visionOS でブラウザーに遷移するopenURL / デフォルトブラウザー
OAuth またはサードパーティーのサインインASWebAuthenticationSession
iOS 26 未満へのバックデプロイ、または従来の WebKit にしかない機能の使用WKWebView フォールバック

新しい API で機能を実現できる場合、iOS 26 以降を対象とする最新の SwiftUI アプリでは WebView と WebPage を優先してください。Apple の WWDC25 のガイダンスでは、SwiftUI アプリ内の既存の UIKit/AppKit WebKit ラッパーは、すべてのフォールバックを一律に削除するのではなく、移行を試すのに適した候補として扱われています。

OAuth に埋め込み Web ビューを使用しないでください。OAuth には引き続き ASWebAuthenticationSession のフローを使用します。

Web コンテンツを表示する

URL の表示だけが必要で、SwiftUI の状態がナビゲーションを制御する場合は、シンプルな WebView(url:) 形式を使用してください。

import SwiftUI
import WebKit

struct ArticleView: View {
    let url: URL

    var body: some View {
        WebView(url: url)
    }
}

リクエストの直接読み込み、状態の監視、JavaScript の呼び出し、またはナビゲーション動作のカスタマイズが必要な場合は、WebPage を作成してください。

WebPage を関連付けられる WebView は、一度に 1 つだけです。複数の Web ビューを同時に表示する場合は、個別の WebPage インスタンスを作成してください。

@Observable
@MainActor
final class ArticleModel {
    let page = WebPage()

    func load(_ url: URL) async throws {
        for try await _ in page.load(URLRequest(url: url)) {
        }
    }
}

struct ArticleDetailView: View {
    @State private var model = ArticleModel()
    let url: URL

    var body: some View {
        WebView(model.page)
            .task {
                try? await model.load(url)
            }
    }
}

詳しい例については、references/loading-and-observation.md を参照してください。

WebPage を使用した読み込みと監視

WebPage は @MainActor の監視可能な型です。SwiftUI でページの状態が必要な場合に使用してください。

一般的な読み込みの入り口:

  • load(URLRequest)
  • load(URL)
  • load(html:baseURL:)
  • load(_:mimeType:characterEncoding:baseURL:)

一般的な監視可能プロパティ:

  • title
  • url
  • isLoading
  • estimatedProgress
  • currentNavigationEvent
  • backForwardList
struct ReaderView: View {
    @State private var page = WebPage()

    var body: some View {
        WebView(page)
            .navigationTitle(page.title ?? "Loading")
            .overlay {
                if page.isLoading {
                    ProgressView(value: page.estimatedProgress)
                }
            }
            .task {
                do {
                    for try await _ in page.load(URLRequest(url: URL(string: "https://example.com")!)) {
                    }
                } catch {
                    // Handle load failure.
                }
            }
    }
}

すべてのナビゲーションに反応する必要がある場合は、単一のプロパティを確認するだけでなく、ナビゲーションのシーケンスを監視してください。

Task {
    do {
        for try await event in page.navigations {
            // Handle started, redirect, committed, or finished events.
        }
    } catch {
        // Handle WebPage.NavigationError or cancellation.
    }
}

より堅牢なパターンや読み込みシーケンスの例については、references/loading-and-observation.md を参照してください。

ナビゲーションポリシー

WebPage.NavigationDeciding を使用すると、リクエストまたはレスポンスに基づいてナビゲーションを許可、キャンセル、またはカスタマイズできます。

一般的な用途:

  • アプリが所有するドメインを埋め込み Web ビュー内にとどめる
  • 外部ドメインをキャンセルし、openURL で引き渡す
  • 特別なコールバック URL をインターセプトする
  • NavigationPreferences を調整する
@MainActor
final class ArticleNavigationDecider: WebPage.NavigationDeciding {
    var urlToOpenExternally: URL?

    func decidePolicy(
        for action: WebPage.NavigationAction,
        preferences: inout WebPage.NavigationPreferences
    ) async -> WKNavigationActionPolicy {
        guard let url = action.request.url else { return .allow }

        if url.host == "example.com" {
            return .allow
        }

        urlToOpenExternally = url
        return .cancel
    }
}

アプリレベルのディープリンクルーティングは、ナビゲーションのスキルで扱ってください。このスキルでは、埋め込み Web コンテンツ内で発生するナビゲーションを扱います。

詳しいパターンについては、references/navigation-and-javascript.md を参照してください。

JavaScript 連携

callJavaScript(_:arguments:in:contentWorld:) を使用して、ページ上で JavaScript 関数を評価します。

ラップされた関数宣言や呼び出し式ではなく、JavaScript 関数の本体を渡してください。信頼できない文字列をスクリプトに埋め込むのではなく、Swift から渡す値には arguments を使用してください。

let script = """
const headings = [...document.querySelectorAll('h1, h2')];
return headings.map(node => ({
    id: node.id,
    text: node.textContent?.trim()
}));
"""

let result = try await page.callJavaScript(script)
let headings = result as? [[String: Any]] ?? []

arguments ディクショナリを介して値を渡し、返された Any を必要な Swift の型にキャストできます。

let result = try await page.callJavaScript(
    "return document.getElementById(sectionID)?.getBoundingClientRect().top ?? null;",
    arguments: ["sectionID": selectedSectionID]
)

空の結果と JavaScript の null の結果は、意図的に処理してください。明示的な return がない場合は nil が返され、明示的な JavaScript の null は NSNull を返します。

重要な境界として、ネイティブの SwiftUI WebKit API は Swift から JavaScript への呼び出しを明確にサポートしていますが、WKScriptMessageHandler に直接置き換えられる明白な手段は提供していません。大まかな JS からネイティブへの通知が必要な場合は、カスタムのナビゲーションやコールバック URL のパターンが使えますが、1 対 1 の置き換えが保証されたものではなく、回避策のパターンとして説明してください。

references/navigation-and-javascript.md を参照してください。

ローカルコンテンツとカスタム URL スキーム

アプリにバンドルされた HTML、オフラインのドキュメント、またはカスタムスキームで提供するアプリのリソースが必要な場合は、WebPage.Configuration と URLSchemeHandler を使用してください。

var configuration = WebPage.Configuration()
configuration.urlSchemeHandlers[URLScheme("docs")!] = DocsSchemeHandler(bundle: .main)

let page = WebPage(configuration: configuration)
for try await _ in page.load(URL(string: "docs://article/welcome")!) {
}

次の用途で使用します。

  • バンドルされたドキュメントや記事コンテンツ
  • オフラインの HTML/CSS/JS アセット
  • カスタムスキームを使用した、アプリ所有のリソース読み込み

通常のリモートコンテンツにカスタムスキームを過剰に使用しないでください。サーバーでホストされるページには、標準の HTTPS を優先してください。

references/local-content-and-custom-schemes.md を参照してください。

WebView のカスタマイズ

WebView の修飾子を使用して、意図するブラウジング体験に合わせます。

便利な修飾子と関連 API:

  • webViewBackForwardNavigationGestures(_:)
  • findNavigator(isPresented:)
  • webViewScrollPosition(_:)
  • webViewOnScrollGeometryChange(...)

ユーザー体験に必要な場合にのみ適用してください。

  • 複数のページを訪れる可能性が高い場合は、戻る/進むジェスチャを有効にします。
  • コンテンツがドキュメント形式の場合は、「ページ内を検索」を追加します。
  • アプリにサイドバー、目次、またはその他の明示的なナビゲーション手段がある場合にのみ、スクロール位置を同期します。

ここでも Apple の HIG が適用されます。適切な場合は戻る/進むナビゲーションをサポートしますが、アプリの Web ビューを汎用ブラウザーにしないでください。

よくある間違い

  • iOS 26 以降の SwiftUI アプリで、まず WebView と WebPage を使うのではなく、デフォルトで WKWebView ラッパーを使用する
  • ASWebAuthenticationSession ではなく、埋め込み Web ビューを OAuth に使用する
  • 状態、JS、またはナビゲーションの制御が必要になってから WebPage を導入する。つまり、最初に単純な WebView(url:) の経路を構築してしまう
  • callJavaScript を WKScriptMessageHandler の直接の置き換えとして扱う
  • 関数本体だけではなく、呼び出し可能な JavaScript ラッパーを callJavaScript に渡す
  • ナビゲーションの失敗によってシーケンスが throw して終了するにもかかわらず、try/catch を使わずに page.navigations を反復処理する
  • 同じ WebPage を、表示中の複数の WebView 値にバインドする
  • 外部ドメインを埋め込み領域の外で開くべき場合にも、すべてのリンクをアプリ内にとどめる
  • macOS や visionOS で SFSafariViewController をプラットフォーム共通の外部ブラウジング手段として扱い、デフォルトブラウザーや openURL の動作を使用しない
  • WebView を中心にブラウザー風のアプリシェルを構築し、目的を絞った埋め込み体験にしない
  • HTTPS で読み込めばよいコンテンツにカスタム URL スキームを使用する
  • WebPage がメインアクターに分離されていることを忘れる

レビューチェックリスト

  • iOS 26 以降の SwiftUI の Web コンテンツでは、WebView と WebPage をデフォルトの方法として使用している
  • 認証フローでは、埋め込み Web ビューではなく ASWebAuthenticationSession を使用している
  • アプリで状態の監視、JS の呼び出し、またはポリシー制御が必要な場合は、WebPage を使用している
  • ナビゲーションポリシーでは、アプリが実際に所有する URL、またはリダイレクトが必要な URL のみをインターセプトしている
  • 適切な場合は、外部ドメインを外部で開いている
  • JavaScript の戻り値は、具体的な Swift 型に安全にキャストしている
  • callJavaScript では関数本体を使用し、Swift の値を arguments 経由で渡している
  • page.navigations のループでは for try await を使用し、スローされたナビゲーションエラーを処理している
  • 表示される各 WebView(page) が、それぞれ異なる WebPage を所有している
  • カスタム URL スキームは、実際にアプリが所有するリソースにのみ使用している
  • 複数ページのブラウジングが想定される場合、戻る/進むジェスチャーまたはコントロールを有効にしている
  • SFSafariViewController は iOS/iPadOS の Safari スタイルのモーダルブラウジングに限定し、macOS および visionOS のブラウザーで開く操作ではプラットフォームのデフォルトのブラウザー動作を使用している
  • Web エクスペリエンスは、薄いブラウザーシェルのように振る舞うのではなく、的を絞ったネイティブの価値を提供している
  • WKWebView へのフォールバックは、デプロイメントターゲットまたは必要な API が利用できないことを理由として正当化されている

参考資料

コメント