> ## Documentation Index
> Fetch the complete documentation index at: https://hellotars.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Swift integration

> Embed a Tars agent in an iOS app with WKWebView and a WKScriptMessageHandler named tarsbridge.

This reference covers the iOS wiring for the [WebView bridge](/docs/developer/mobile/webview-bridge). The dashboard generates a starting snippet with your agent's real URL: open **Distribute** → **Mobile App**, turn on **Enable Bridge Events**, and select the **Swift/UIKit** tab. The snippet on this page also relays widget messages to the app and posts `tars:init` into the page, which the bridge requires.

## Requirements

* `WKWebView` from WebKit
* A `WKScriptMessageHandler` registered under the name `tarsbridge`
* **Enable Bridge Events** turned on for the agent

## Snippet

```swift theme={null}
import WebKit

class AgentChatViewController: UIViewController, WKScriptMessageHandler {
    var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let config = WKWebViewConfiguration()
        config.userContentController.add(self, name: "tarsbridge")

        // Forwards every tars: message from the page to the tarsbridge handler
        let relayScript = """
        window.addEventListener('message', function(event) {
            var msg = event.data;
            if (msg && typeof msg.type === 'string' && msg.type.indexOf('tars:') === 0) {
                window.webkit.messageHandlers.tarsbridge.postMessage(msg);
            }
        });
        """
        config.userContentController.addUserScript(
            WKUserScript(source: relayScript, injectionTime: .atDocumentStart, forMainFrameOnly: true)
        )

        let initScript = """
        window.addEventListener('load', function() {
            window.postMessage({
                type: 'tars:init', platform: 'ios', version: '1.0',
                chrome: { showHeader: false, showCloseButton: true }
            }, '*');
        });
        """
        config.userContentController.addUserScript(
            WKUserScript(source: initScript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        )

        webView = WKWebView(frame: view.bounds, configuration: config)
        view.addSubview(webView)
        webView.load(URLRequest(url: URL(string: "https://YOUR_WIDGET_HOST/widget/YOUR_AGENT_ID?region=YOUR_REGION")!))
    }

    func userContentController(_ controller: WKUserContentController, didReceive message: WKScriptMessage) {
        guard let body = message.body as? [String: Any],
              let type = body["type"] as? String else { return }
        switch type {
        case "tars:navigate":
            if let route = body["route"] as? String {
                // Navigate to the native route, params in body["params"]
                _ = route
            }
        case "tars:close":
            dismiss(animated: true)
        case "tars:data":
            // body["variables"] holds the captured conversation variables
            break
        default: break
        }
    }
}
```

## Message wiring

| Piece                                                 | Role                                                                                                                                       |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `userContentController.add(self, name: "tarsbridge")` | Registers the widget → app channel. The handler name must match the one the relay script calls                                             |
| Relay `WKUserScript`                                  | Forwards `tars:` messages to the handler. The widget posts its messages inside the page, so without the relay the handler receives nothing |
| `userContentController(_:didReceive:)`                | Receives widget messages. `message.body` is a dictionary, branch on its `type`                                                             |
| Init `WKUserScript`                                   | Posts `tars:init` into the page after it loads                                                                                             |
| `webView.evaluateJavaScript`                          | Sends later app → widget messages, such as `tars:inject` or `tars:command`                                                                 |

The init script runs on the page load event, which can fire before the widget mounts. A `tars:init` posted that early is dropped and never answered. Resend `tars:init` until a `tars:ready` carrying `capabilities` arrives, because a repeat init after the handshake is ignored. The snippet above posts the init once, so add the resend before you ship it.

Handle at least `tars:navigate`, `tars:data`, and `tars:close`: the three capabilities the bridge declares in its `tars:ready` reply to `tars:init`. Field-level payloads for every message are in the [bridge events reference](/docs/developer/mobile/bridge-events).

## Related pages

* [The WebView bridge](/docs/developer/mobile/webview-bridge)
* [Bridge events reference](/docs/developer/mobile/bridge-events)
* [React Native integration](/docs/developer/mobile/react-native)
* [Kotlin integration](/docs/developer/mobile/kotlin)
