Skip to main content

Native iOS (Swift)

Integrate FaceGuard into native iOS apps using WKWebView.

Setup

Add NSCameraUsageDescription to your Info.plist:

Info.plist
<key>NSCameraUsageDescription</key>
<string>FaceGuard needs camera access for face verification</string>

Implementation

FaceGuardViewController.swift
import WebKit

class FaceGuardViewController: UIViewController, WKScriptMessageHandler {
private var webView: WKWebView!

func startVerification(token: String) {
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = []

// Listen for postMessage events
config.userContentController.add(self, name: "surtHandler")

// Inject bridge: forward postMessage to native handler
let script = WKUserScript(
source: """
window.addEventListener('message', function(e) {
if (e.data && (e.data.type === 'surt:ready' || e.data.action === 'close')) {
window.webkit.messageHandlers.surtHandler.postMessage(JSON.stringify(e.data));
}
});
""",
injectionTime: .atDocumentStart,
forMainFrameOnly: false
)
config.userContentController.addUserScript(script)

webView = WKWebView(frame: view.bounds, configuration: config)
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(webView)

let url = URL(string: "https://faceguard.surt.com/intro?token=\(token)")!
webView.load(URLRequest(url: url))
}

func userContentController(
_ controller: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard let body = message.body as? String,
let data = body.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
else { return }

if let action = json["action"] as? String, action == "close" {
let reason = json["reason"] as? String ?? ""
let confidence = json["confidence"] as? Double ?? 0

switch reason {
case "approved", "bypass_active":
handleApproved(confidence: confidence)
case "rejected":
handleRejected(confidence: confidence)
case "canceled":
handleCanceled()
case "error", "no_base_photo":
handleError(message: json["error"] as? String ?? reason)
default:
break
}
}
}

func handleApproved(confidence: Double) { /* Grant access */ }
func handleRejected(confidence: Double) { /* Deny access */ }
func handleCanceled() { /* User closed */ }
func handleError(message: String) { /* Show error */ }
}

How the Bridge Works

WKWebView doesn't natively receive postMessage events from iframes. The injected JavaScript script listens for message events and forwards them to the native WKScriptMessageHandler via window.webkit.messageHandlers.

Safe Areas

Use Auto Layout with safeAreaLayoutGuide to avoid the notch and home indicator.