Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.go.gbgplc.com/llms.txt

Use this file to discover all available pages before exploring further.

This guide covers how to embed the bridge-connected WebView in your iOS application, including SwiftUI and UIKit integration patterns.

SwiftUI Integration

For SwiftUI apps, BridgeWebView is the fastest path: drop it into a view, give it a URL and a host, and you have a fully wired bridge. Use the patterns below for state observation, child-view passing, and dynamic URLs.

Using BridgeWebView

BridgeWebView is the recommended way to embed a bridge-connected WebView in SwiftUI. It handles all setup automatically.
import GBGBridge
import SwiftUI

struct JourneyScreen: View {
    @StateObject private var host = BridgeHost(hostVersion: "1.0.0")

    var body: some View {
        VStack {
            BridgeWebView(
                url: URL(string: "https://journey.example.com")!,
                host: host
            )
        }
        .onAppear {
            host.documentCapture.handler = { [weak host] request in
                guard let host else { return .cancelled(reason: "Host deallocated") }
                return await host.documentCapture.awaitCompletion()
            }
        }
    }
}

Observing Bridge State

Because BridgeHost is an ObservableObject, you can react to state changes directly in your SwiftUI views.
struct JourneyScreen: View {
    @StateObject private var host = BridgeHost(hostVersion: "1.0.0")

    var body: some View {
        ZStack {
            BridgeWebView(
                url: URL(string: "https://journey.example.com")!,
                host: host
            )

            // Show error banner when an error occurs
            if let error = host.lastError {
                VStack {
                    Text(error)
                        .foregroundColor(.white)
                        .padding()
                        .background(Color.red.cornerRadius(8))
                        .padding()
                    Spacer()
                }
                .transition(.move(edge: .top))
            }
        }
    }
}

Passing the Host to Child Views

Use @ObservedObject in child views that receive the host as a parameter.
struct JourneyContainer: View {
    @StateObject private var host = BridgeHost(hostVersion: "1.0.0")

    var body: some View {
        NavigationStack {
            JourneyWebView(host: host)
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        MessageCountBadge(host: host)
                    }
                }
        }
    }
}

struct JourneyWebView: View {
    @ObservedObject var host: BridgeHost

    var body: some View {
        BridgeWebView(
            url: URL(string: "https://journey.example.com")!,
            host: host
        )
    }
}

struct MessageCountBadge: View {
    @ObservedObject var host: BridgeHost

    var body: some View {
        Text("\(host.receivedMessages.count)")
            .font(.caption)
            .padding(6)
            .background(Color.blue)
            .clipShape(Circle())
            .foregroundColor(.white)
    }
}

Dynamic URL Changes

BridgeWebView supports URL changes. If you update the URL binding, the WebView navigates to the new URL.
struct DynamicJourney: View {
    @StateObject private var host = BridgeHost(hostVersion: "1.0.0")
    @State private var journeyURL = URL(string: "https://journey.example.com/step1")!

    var body: some View {
        VStack {
            BridgeWebView(url: journeyURL, host: host)

            Button("Next Step") {
                journeyURL = URL(string: "https://journey.example.com/step2")!
            }
        }
    }
}

UIKit Integration

For UIKit apps (or SwiftUI apps that need a custom WKWebViewConfiguration), use BridgeWebViewConfigurator to wire the bridge into a WebView you create yourself.

Using BridgeWebViewConfigurator

For UIKit-based apps, use BridgeWebViewConfigurator to configure a WKWebView with the bridge.
import GBGBridge
import UIKit
import WebKit

class JourneyViewController: UIViewController {
    private let host: BridgeHost
    private var webView: WKWebView!

    init() {
        self.host = BridgeHost(hostVersion: "1.0.0")
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Option A: Create a new configured WebView
        webView = BridgeWebViewConfigurator.makeWebView(host: host)

        // Option B: Configure an existing WebView
        // let config = WKWebViewConfiguration()
        // webView = WKWebView(frame: .zero, configuration: config)
        // BridgeWebViewConfigurator.configure(webView, host: host)

        webView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(webView)

        NSLayoutConstraint.activate([
            webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])

        let url = URL(string: "https://journey.example.com")!
        webView.load(URLRequest(url: url))
    }
}

Configuring an Existing WebView

If you have a WKWebView created with a custom WKWebViewConfiguration, use BridgeWebViewConfigurator.configure(_:host:).
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true

let webView = WKWebView(frame: .zero, configuration: config)
BridgeWebViewConfigurator.configure(webView, host: host)
This approach is useful when you need custom WebView settings (media playback, data detection, process pools, etc.) that makeWebView doesn’t configure.

Lifecycle Considerations

The BridgeHost and the WebView have different ownership semantics β€” the host holds a weak reference to the WebView, and handlers should be set up before content loads. Keep these in mind to avoid leaks, attachment errors, and missed messages.

Host Lifetime

The BridgeHost should live at least as long as the WebView. In SwiftUI, use @StateObject to ensure the host isn’t recreated on view updates. In UIKit, hold a strong reference as a property.

WebView Detachment

BridgeHost holds a weak reference to the WebView. If the WebView is deallocated while the host is still alive, attempts to send messages will set lastError to "WebView not attached". This is by design β€” it prevents retain cycles.

Re-attaching a WebView

If you need to replace the WebView (e.g., after a navigation reset), call BridgeWebViewConfigurator.configure(_:host:) with the new WebView. The host automatically updates its internal reference.

Setting Up Handlers

Set handlers on typed slots or register custom capabilities before the WebView loads content. If the web journey sends a request before a handler is available, the request goes to pendingRequests.
// Set up typed slots
host.documentCapture.handler = { [weak host] request in
    guard let host else { return .cancelled(reason: "Host deallocated") }
    return await host.documentCapture.awaitCompletion()
}

// Register custom capabilities
host.registerCustomCapability("nfc.read") { request, responder in
    // Handle NFC
}

// Then load the journey
webView.load(URLRequest(url: journeyURL))
BridgeWebView provides a basic WKNavigationDelegate via its Coordinator. If you need custom navigation behavior (e.g., intercepting links, handling authentication challenges), you have two options:
  1. UIKit path: Use BridgeWebViewConfigurator and set your own navigation delegate on the WebView.
  2. SwiftUI path: Build a custom UIViewRepresentable that uses BridgeWebViewConfigurator internally.
struct CustomBridgeWebView: UIViewRepresentable {
    let url: URL
    @ObservedObject var host: BridgeHost

    func makeUIView(context: Context) -> WKWebView {
        let webView = BridgeWebViewConfigurator.makeWebView(host: host)
        webView.navigationDelegate = context.coordinator
        webView.load(URLRequest(url: url))
        return webView
    }

    func updateUIView(_ webView: WKWebView, context: Context) {}

    func makeCoordinator() -> Coordinator {
        Coordinator()
    }

    class Coordinator: NSObject, WKNavigationDelegate {
        func webView(
            _ webView: WKWebView,
            decidePolicyFor navigationAction: WKNavigationAction
        ) async -> WKNavigationActionPolicy {
            // Custom navigation logic
            if navigationAction.navigationType == .linkActivated,
               let url = navigationAction.request.url,
               url.host != "journey.example.com" {
                await UIApplication.shared.open(url)
                return .cancel
            }
            return .allow
        }
    }
}

Next Steps