A lightweight WebView–Native bridge for iOS and macOS, built on WebKit’s WKWebView and WKScriptMessageHandler.
You need a Web implementation: use webnat-web (or a compatible client) in the page loaded by the web view. This repo is the Native side only.
| Version | |
|---|---|
| Swift | 5.5+ |
| iOS | 12.0+ |
| macOS | 11.0+ |
| Xcode | 14+ recommended (Swift 6–friendly toolchains) |
Threading: Webnat and related APIs are main-thread oriented (@MainActor). Call initialize, of, listeners, and sends from the main thread unless your app’s architecture already guarantees the same execution context as the web view.
Concurrency: async/await method calls, AsyncStream broadcast listening, and related APIs require iOS 13+ (and the equivalent macOS version for those APIs). On older iOS versions, use the callback-based method overload.
- Multi-platform — iOS 12+ and macOS 11+
- iframe support — message forwarding between the main frame and iframes
- Three modes — raw messages, broadcast, and RPC-style methods
- Timeout and cancellation — built-in timeout and cooperative cancel
- Concurrency — ready for Swift 6 language mode in dependent projects
Open Example/Example.xcodeproj in Xcode. Run the Example target and load a page that uses webnat-web so connections and messages have a peer to talk to.
Pin versions to a Git tag that exists on this repo. Release tags should match Sources/Webnat/Version.swift and Webnat.podspec.
dependencies: [
.package(url: "https://github.com/auhgnayuo/webnat-darwin.git", from: "1.0.3")
]In Xcode: File → Add Package Dependencies… → enter https://github.com/auhgnayuo/webnat-darwin.git → add the Webnat product.
pod 'Webnat', :git => 'https://github.com/auhgnayuo/webnat-darwin.git', :tag => '1.0.3'Track a branch:
pod 'Webnat', :git => 'https://github.com/auhgnayuo/webnat-darwin.git', :branch => 'main'Then pod install. For :tag, keep the tag aligned with s.version in Webnat.podspec. Maintainer checklist (lint, tagging): CONTRIBUTING.md.
| Platform | Repository |
|---|---|
| Web (JavaScript/TypeScript) | webnat-web |
| Android (Kotlin) | webnat-android |
| HarmonyOS (ArkTS) | webnat-ohos |
import Webnat
import WebKit
let configuration = WKWebViewConfiguration()
Webnat.initialize(webViewConfiguration: configuration)
let webView = WKWebView(frame: .zero, configuration: configuration)
let webnat = Webnat.of(webView)Connections are started from JavaScript. Native code reads webnat.connections.
let connections = webnat.connections
print("Active connections: \(connections.count)")
if let connection = connections["connection-id"] {
print("Connection found:", connection.id)
print("Attributes:", connection.attributes ?? [:])
}// Raw
webnat.raw("Hello from Native!", connection: connection)
let rawListener: RawBlockListener = { raw, connection in
print("From \(connection.id):", raw)
}
webnat.onRaw(listener: rawListener)
// Broadcast
webnat.broadcast(name: "userLoggedIn", param: ["userId": 123], connection: connection)
let broadcastListener: BroadcastBlockListener = { param, connection in
print("Broadcast from \(connection.id):", param ?? "nil")
}
webnat.onBroadcast(name: "userLoggedIn", listener: broadcastListener)
// Stream broadcasts (iOS 13+)
if #available(iOS 13.0, *) {
Task {
for await (param, connection) in webnat.listenBroadcast(name: "userLoggedIn") {
print("Broadcast from \(connection.id):", param ?? "nil")
}
}
}
// Call a Web-side method (callback; works on all supported iOS versions)
webnat.method(
"getUserInfo",
param: ["userId": 123],
timeout: 5.0,
connection: connection
) { result, error in
if let error = error {
print("Error:", error)
} else {
print("User info:", result ?? "nil")
}
}
// Same call with async/await (iOS 13+)
if #available(iOS 13.0, *) {
Task {
do {
let result = try await webnat.method(
"getUserInfo",
param: ["userId": 123],
timeout: 5.0,
connection: connection
)
print("User info:", result ?? "nil")
} catch {
print("Error:", error)
}
}
}
// Register a method for the Web to invoke
let methodListener: MethodBlockListener = { param, callback, notify, connection in
let userId = param?["userId"] as? Int ?? 0
notify(["progress": 50])
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
callback(["userId": userId, "name": "User"], nil)
}
return {
// Cancellation logic
}
}
webnat.onMethod(name: "getUserInfo", listener: methodListener)The package ships Sources/Webnat/PrivacyInfo.xcprivacy as a resource. When you integrate via SPM or CocoaPods, include it in the app bundle as required by Apple’s privacy manifest rules.
See CONTRIBUTING.md.