WebKitとは

WebKitとは、iOS開発でWeb機能を提供するフレームワークです。
従来の課題
WebKitではWKWebView
を使うことでWebを表示できます。
しかしこれらはSwiftUIでは直接使用できず、UIViewRepresentable
でラップする必要がありました。
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
let url: URL
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ webView: WKWebView, context: Context) {
let request = URLRequest(url: url)
webView.load(request)
}
}
struct ContentView: View {
var body: some View {
WebView(url: URL(string: "https://www.apple.com")!)
.navigationTitle("Web View")
}
}
WWDC25にてSwiftUI対応
しかしWWDC 2025にてWebKitがSwiftUI向けに拡張され、 WebView
というものが使えるようになりました。これにより、SwiftUIでもWeb表示をより簡潔に記述できるようになりました。

今回はWebViewの基本的な使い方から、応用例、さらにちょっとだけ内部設計にまで踏み込んで解説します。
動作環境
- Xcode 26 beta 1
- Swift6.2
基本的な使い方

ではさっそく、WebViewの使い方について解説していきます。
まずはシンプルに指定したURLを表示してみましょう。
import SwiftUI
import WebKit
struct ContentView: View {
var body: some View {
WebView(url: URL(string: "https://google.com")!)
.ignoresSafeArea(edges: .bottom)
}
}
WKWebViewをUIViewRepresentable
でラップしてた今までの方法と比べると、すごく簡潔になりました。
表示するURLを切り替えることも可能です。
import SwiftUI
import WebKit
struct ContentView: View {
@State private var url: URL? = URL(string: "https://google.com")
var body: some View {
NavigationStack {
WebView(url: url)
.ignoresSafeArea(edges: .bottom)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
url = URL(string: "https://apple.com")
} label: {
Image(systemName: "apple.logo")
}
}
}
}
}
}
WebPageクラス
WebPageとは

WebPageとは、Webコンテンツを表現し、制御するためのObservableクラスです。表示しているURLやタイトル、ローディング状況などを取得することができます。
WebViewにはURLで初期化する以外に、WebPageを受け取るinitも持っています。
import SwiftUI
import WebKit
struct ContentView: View {
@State private var page = WebPage()
var body: some View {
WebView(page) // WebPageを使って初期化
.ignoresSafeArea(edges: .bottom)
.onAppear {
page.load(URLRequest(url: URL(string:"https://google.com")!))
}
}
}
次の例はWebPageを使って、Webコンテンツの情報を取得しています。
struct ContentView: View {
@State private var page = WebPage()
var body: some View {
NavigationStack {
WebView(page)
.navigationTitle(page.title)
.navigationBarTitleDisplayMode(.inline)
.ignoresSafeArea(edges: .bottom)
.overlay {
if page.isLoading {
Color.black.opacity(0.1)
.ignoresSafeArea()
ProgressView()
}
}
}
.onAppear {
page.load(URLRequest(url: URL(string:"https://google.com")!))
}
}
}
取得可能な情報
WebPageを使うことで、以下のような値を取得することができます。
webPage.title // ページタイトル
webPage.url // 現在のURL
webPage.estimatedProgress // 読み込み進捗(0.0〜1.0)
webPage.themeColor // ページのテーマカラー
webPage.backForwardList // ページ履歴のリスト
他にもありますが、詳しくは公式ドキュメントをご覧ください。
後で詳しく解説しますが、WebPageは内部でWKWebViewを保持しており、WKWebViewの値を自身に反映しています。
実践的な活用例
ページ内の文字列検索
.findNavigatorを使うことで、ページ内の文字列検索ができます。
struct ContentView: View {
@State private var page = WebPage()
@State private var showFindNavigator = false
var body: some View {
NavigationStack {
WebView(page)
.ignoresSafeArea(edges: .bottom)
.findNavigator(isPresented: $showFindNavigator)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
showFindNavigator.toggle()
} label: {
Image(systemName: "magnifyingglass")
}
}
}
}
.onAppear {
page.load(URLRequest(url: URL(string:"https://google.com")!))
}
}
}
JavaScript連携
WebPageのcallJavaScriptを使うことで、Webコンテンツに対してJavaScriptを簡単に適応できるようになります。
まずはコンテンツ内の要素を変更する方法を紹介します。
struct ContentView: View {
@State private var page = WebPage()
var body: some View {
VStack {
Button("文字を赤にする") {
Task {
try await page.callJavaScript("document.body.style.color = 'red';")
}
}
WebView(page)
.onAppear {
page.load(URLRequest(url: URL(string:"https://www.lipsum.com/")!))
}
}
}
}
次にコンテンツの値を受け取る方法を紹介します。callJavaScriptは返り値で、JavaScriptの結果を返してくれます。
struct ContentView: View {
@State private var page = WebPage()
@State private var pageTitle: String = ""
@State private var pageHeight: CGFloat = 0
var body: some View {
VStack {
Text("ページタイトル: \(pageTitle)")
Text("ページの高さ: \(pageHeight)")
Button("情報を取得") {
Task {
await getInfo()
}
}
WebView(page)
.onAppear {
page.load(URLRequest(url: URL(string:"https://apple.com")!))
}
}
}
private func getInfo() async {
if let result = try? await page.callJavaScript("return document.title"),
let title = result as? String {
pageTitle = title
}
if let result = try? await page.callJavaScript("return document.body.scrollHeight"),
let height = result as? CGFloat {
pageHeight = height
}
}
}
戻るボタンの実装
戻るボタンはWebPageのbackForwardListを使うことで実装できます。
次の例は、2ページ目以降であればボタンを表示して、押下することで前のページに戻る方法です。
struct ContentView: View {
@State private var page = WebPage()
var body: some View {
NavigationStack {
WebView(page)
.ignoresSafeArea(edges: .bottom)
.navigationTitle(page.title)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
if !page.backForwardList.backList.isEmpty {
Button {
page.load(page.backForwardList.backList.last!)
} label: {
Image(systemName: "chevron.left")
.foregroundStyle(.blue)
}
}
}
}
}
.onAppear {
page.load(URLRequest(url: URL(string:"https://google.com")!))
}
}
}
特定のドメインのみを読み込む
アプリの要件によっては、特定のドメインだけを読み込みたいこともあります。
そんな時は、NavigationDecidingを使用します。

次の例では、apple.com
だけを許可し、それ以外のドメインは外部ブラウザを起動するようにしています。
struct ContentView: View {
@Environment(\.openURL) private var openURL
@State private var page = WebPage()
var body: some View {
NavigationStack {
WebView(page)
.ignoresSafeArea(edges: .bottom)
}
.onAppear {
let navigationDecider = NavigationDeciding()
navigationDecider.urlToOpen = { url in
if let url {
openURL(url)
}
}
page = WebPage(navigationDecider: navigationDecider)
page.load(URLRequest(url: URL(string:"https://apple.com")!))
}
}
}
final class NavigationDeciding: WebPage.NavigationDeciding {
var urlToOpen: ((URL?) -> Void)?
func decidePolicy(for action: WebPage.NavigationAction, preferences: inout WebPage.NavigationPreferences) async -> WKNavigationActionPolicy {
let url = action.request.url
if url?.host() == "apple.com" || url?.host() == "www.apple.com" {
return .allow
}
urlToOpen?(url)
return .cancel
}
}
WebViewとWebPageの関係性
WebKitはOSSなので、誰でもソースコードを閲覧できます。
ここからは、少しだけ内部設計に踏み込んでWebViewとWebPageの関係性について解説します。
WebPageは内部にWKWebViewを保持しています。WKWebViewの値(title, urlなど)を監視することで、自身の値を更新しています。
// このWebPageWebViewはWKWebViewを継承してる
public lazy var backingWebView: WebPageWebView = {
let webView = WebPageWebView(frame: .zero, configuration: WKWebViewConfiguration(configuration))
webView.navigationDelegate = backingNavigationDelegate
webView.uiDelegate = backingUIDelegate
#if os(macOS)
webView._usePlatformFindUI = false
#endif
return webView
}()
public final class WebPageWebView: WKWebView {
// ...
}
そしてWebViewは内部でWebPageを保持しています。
WebViewにURLを渡して初期化する時も、内部ではWebPageを生成しています。
public init(url: URL?) {
self.storage = .state(State(initialValue: WebPage()), url)
// ↑ ここで WebPage() を作成
}
WebViewはView構造体なので、bodyを見てみましょう。
public var body: some View {
representable
}
@ViewBuilder
private var representable: some View {
WebViewRepresentable(page: storage.webPage, safeAreaInsets: proxy.safeAreaInsets)
}
そしてこのWebViewRepresentableの中で、WebPageのWKWebViewを参照しています。
func makePlatformView(context: Context) -> CocoaWebViewAdapter {
precondition(!page.isBoundToWebView, "This web page is already bound to another web view.")
let parent = CocoaWebViewAdapter()
parent.webView = page.backingWebView // ← 実際のWKWebViewを設定
page.isBoundToWebView = true
return parent
}
つまりWebPageが中核となって、UIKit(WKWebView)とSwiftUI(WebView)の橋渡し的な役割を果たしています。
まとめると、こんな感じです。
WebView → WebPageのbackingWebViewを見て、Webを表示
WebPage → データの管理、UIKitとSwiftUIの橋渡し

まとめ
今回はWWDC25で発表された、SwiftUI向けのWebKitについて解説しました。
新たに追加されたWebViewを使えば、SwiftUIでも簡単にWebコンテンツを表示できることがわかったと思います。
ただこれらの機能が使えるのは、iOS26以降なのでそこは注意です。
ぜひご自身の手で色々と試してみてください。
ここまでのご閲覧ありがとうございました!
コメント