> For the complete documentation index, see [llms.txt](https://docs.livecaller.io/livecaller/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.livecaller.io/livecaller/mobile-sdks/webview-ios-swiftui.md).

# WebView iOS SwiftUI

## LiveCaller Chat Widget Integration (iOS SwiftUI)

This guide explains how to embed the LiveCaller chat widget into an iOS SwiftUI app using **WKWebView**.

***

## 🏠 Home Screen

Home Screen with Chat Navigation:

```swift
import SwiftUI

struct HomeView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("Home")
                    .font(.largeTitle)
                    .padding()

                NavigationLink(destination: ChatView()) {
                    Text("Open Chat")
                        .foregroundColor(.white)
                        .padding()
                        .background(Color.blue)
                        .cornerRadius(8)
                }
            }
            .navigationTitle("Home")
        }
    }
}
```

***

## 💬 Chat Screen

Loads the chat and handles errors with overlays.

```swift
struct ChatView: View {
    @State private var isLoading = true
    @State private var errorMessage: String?

    var body: some View {
        ZStack {
            WebView(htmlString: chatHtml, isLoading: $isLoading, errorMessage: $errorMessage)
                .opacity(errorMessage == nil ? 1 : 0)

            if isLoading {
                VStack {
                    ProgressView()
                        .progressViewStyle(CircularProgressViewStyle())
                    Text("Loading chat...")
                        .padding(.top, 8)
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.white)
            }

            if let error = errorMessage {
                VStack(spacing: 16) {
                    Image(systemName: "exclamationmark.triangle.fill")
                        .resizable()
                        .frame(width: 64, height: 64)
                        .foregroundColor(.red)
                    Text(error)
                        .multilineTextAlignment(.center)
                    Button("Retry") {
                        errorMessage = nil
                        isLoading = true
                    }
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(8)
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.white)
            }
        }
        .navigationTitle("LiveCaller Chat")
        .navigationBarTitleDisplayMode(.inline)
    }
}

```

***

## 🌐 WebView Wrapper

Handles the response for loading the chat HTML.

```swift
import WebKit

struct WebView: UIViewRepresentable {
    let htmlString: String
    @Binding var isLoading: Bool
    @Binding var errorMessage: String?

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

    func makeUIView(context: Context) -> WKWebView {
        let configuration = WKWebViewConfiguration()
        configuration.preferences.javaScriptEnabled = true

        let webView = WKWebView(frame: .zero, configuration: configuration)
        webView.navigationDelegate = context.coordinator
        webView.loadHTMLString(htmlString, baseURL: URL(string: "https://cdn.livecaller.io"))
        return webView
    }

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

    class Coordinator: NSObject, WKNavigationDelegate {
        var parent: WebView

        init(_ parent: WebView) {
            self.parent = parent
        }

        func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
            parent.isLoading = true
            parent.errorMessage = nil
        }

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            parent.isLoading = false
        }

        func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
            parent.isLoading = false
            parent.errorMessage = "Failed to load chat: \(error.localizedDescription)"
        }

        func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
            parent.isLoading = false
            parent.errorMessage = "Failed to load chat: \(error.localizedDescription)"
        }

        func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) -> WKNavigationActionPolicy {
            if let url = navigationAction.request.url?.absoluteString,
               url.starts(with: "https://cdn.livecaller.io") ||
               url.starts(with: "https://livecaller.io") ||
               url.starts(with: "data:") {
                return .allow
            }
            return .cancel
        }
    }
}

```

***

## 📄 LiveCaller HTML

```swift
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<title>LiveCaller Chat</title>
<style>
html, body { margin:0; padding:0; height:100%; width:100%; overflow:hidden; background:#ffffff; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;}
#live-caller-widget { position: fixed; top:0; left:0; right:0; bottom:0; width:100%; height:100%; }
.loading { display:flex; align-items:center; justify-content:center; height:100vh; flex-direction:column; color:#666; }
.spinner { border:3px solid #f3f3f3; border-top:3px solid #2196F3; border-radius:50%; width:40px; height:40px; animation:spin 1s linear infinite; margin-bottom:16px; }
@keyframes spin {0% { transform: rotate(0deg);} 100% { transform: rotate(360deg);}}
</style>
</head>
<body>
<div id="live-caller-widget">
<div class="loading">
<div class="spinner"></div>
<p>Initializing LiveCaller...</p>
</div>
</div>
<script>
console.log('Starting LiveCaller initialization...');
(function(w,t,c,p,s,l){
p=new Promise(function(resolve,reject){
w[c]={client:()=>p};
l=document.getElementById('live-caller-widget');
s=document.createElement(t);
s.async=true;
s.setAttribute('data-livecaller','script');
s.src='https://cdn.livecaller.io/js/app.js';
s.onload=function(){ console.log('LiveCaller script loaded successfully'); resolve(w[c]); };
s.onerror=function(error){ console.error('Failed to load LiveCaller script:',error); l.innerHTML='<div class="loading"><p>Failed to load chat. Please check your connection.</p></div>'; reject(error); };
(document.head||document.body).appendChild(s);
});
return p;
})(window,'script','LiveCaller').then(function(){
console.log('Configuring LiveCaller...');
LiveCaller.config.merge({
widget:{id:'your widget id here'},
app:{locale:'en',}
});
console.log('Starting LiveCaller...');
LiveCaller.liftOff();
setTimeout(function(){ console.log('Opening LiveCaller widget...'); LiveCaller.$emit('ui.widget.open'); },500);
}).catch(function(error){
console.error('LiveCaller initialization failed:',error);
document.getElementById('live-caller-widget').innerHTML='<div class="loading"><p>Chat initialization failed. Please try again.</p></div>';
});
</script>
</body>
</html>

```

***


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.livecaller.io/livecaller/mobile-sdks/webview-ios-swiftui.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
