Loading...
Loading...
Embeds and controls web content in SwiftUI with WebKit for SwiftUI, including WebView, WebPage, navigation policies, JavaScript execution, observable page state, link interception, local HTML or data loading, and custom URL schemes. Use when building iOS 26+ article/detail views, help centers, in-app documentation, or other embedded web experiences backed by HTML, CSS, and JavaScript.
npx skill4agent add dpearson2699/swift-ios-skills swiftui-webkit| Need | Default choice |
|---|---|
| Embedded app-owned web content in SwiftUI | |
| Simple external site presentation with Safari behavior | |
| OAuth or third-party sign-in | |
| Back-deploy below iOS 26 or use missing legacy-only WebKit features | |
WebViewWebPageASWebAuthenticationSessionWebView(url:)import SwiftUI
import WebKit
struct ArticleView: View {
let url: URL
var body: some View {
WebView(url: url)
}
}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.mdWebPage@MainActorload(URLRequest)load(URL)load(html:baseURL:)load(_:mimeType:characterEncoding:baseURL:)titleurlisLoadingestimatedProgresscurrentNavigationEventbackForwardListstruct 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 {
for await event in page.navigations {
// Handle finish, redirect, or failure events.
}
}references/loading-and-observation.mdWebPage.NavigationDecidingopenURLNavigationPreferences@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
}
}references/navigation-and-javascript.mdcallJavaScript(_:arguments:in:contentWorld:)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]] ?? []argumentsAnylet result = try await page.callJavaScript(
"return document.getElementById(sectionID)?.getBoundingClientRect().top ?? null;",
arguments: ["sectionID": selectedSectionID]
)WKScriptMessageHandlerreferences/navigation-and-javascript.mdWebPage.ConfigurationURLSchemeHandlervar 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")!) {
}
references/local-content-and-custom-schemes.mdwebViewBackForwardNavigationGestures(_:)findNavigator(isPresented:)webViewScrollPosition(_:)webViewOnScrollGeometryChange(...)WKWebViewWebViewWebPageASWebAuthenticationSessionWebPageWebView(url:)callJavaScriptWKScriptMessageHandlerWebPageWebViewWebPageASWebAuthenticationSessionWebPageWKWebViewreferences/loading-and-observation.mdreferences/navigation-and-javascript.mdreferences/local-content-and-custom-schemes.mdreferences/migration-and-fallbacks.md