WebView iOS SwiftUI

LiveCaller Chat Widget Integration (iOS SwiftUI)

This guide explains how to integrate LiveCaller Chat Widget into an iOS SwiftUI app using a WKWebView wrapper.


๐Ÿ  Home Screen

A simple home screen with navigation to the chat:

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

Implements the chat view with loading and error handling overlays.

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

Responsible for loading the chat HTML.

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

Embedded HTML with the LiveCaller widget script.

<!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>

Last updated