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