iOS WebView Integration
This guide explains how to embed the Capture App inside a native iOS application using WKWebView.
Embed the Capture App in a native iOS App
The implementation:
- Launches the hosted Capture App inside your app
- Handles camera permissions correctly
- Grants WebKit-level media access (iOS 15+)
- Preserves a seamless user experience
How it works
- App screen loads.
- Camera permission is checked.
- If granted:
- WebView is initialized.
- Capture URL is loaded.
- If denied:
- User is directed to enable permission in Settings.
- Web capture experience runs within the app.
Requirements
Minimum platform
- Xcode 14+
- iOS 14+ (iOS 15+ recommended)
Required frameworks
import UIKit
import WebKit
import AVFoundationRequired Info.plist Key
Add the following key:
NSCameraUsageDescriptionExample:
Camera access is required to complete the verification process.
Failure to include this key will cause runtime crashes when requesting camera permission.
Step 1 - Request camera permission
Camera permission must be granted before loading the Capture App.
private func requestCameraPermission(completion: @escaping (Bool) -> Void) {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
completion(true)
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { granted in
completion(granted)
}
case .denied, .restricted:
completion(false)
@unknown default:
completion(false)
}
}Behavior
- If authorized → continues immediately\
- If first request → iOS permission prompt appears\
- If denied → must redirect user to Settings
Step 2 - Configure WKWebView
Configure the WebView for mobile rendering and inline media playback.
private func setupWebView() {
let configuration = WKWebViewConfiguration()
let preferences = WKWebpagePreferences()
preferences.preferredContentMode = .mobile
configuration.defaultWebpagePreferences = preferences
configuration.allowsInlineMediaPlayback = true
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.uiDelegate = self
self.webView = webView
view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: view.topAnchor),
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}Key configuration details
preferredContentMode = .mobileensures mobile layoutallowsInlineMediaPlayback = trueprevents fullscreen media hijacking- Fullscreen constraints provide consistent UX
Step 3 - Load the Capture App URL
private func loadWebPage() {
guard let webView = self.webView,
let url = URL(string: "https://CAPTURE_APP_URL") else { return }
let request = URLRequest(url: url)
webView.load(request)
}Requirements
- Must use HTTPS
- Replace
"https://YOUR_CAPTURE_URL"with the provided Capture App URL - Avoid force-unwrapping in production
Step 4 - Handle denied permission
If camera access is denied, direct the user to Settings.
private func showPermissionAlert() {
let alert = UIAlertController(
title: "Camera Access Required",
message: "Please enable camera access in Settings to continue.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { _ in
self.openAppSettings()
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
}
private func openAppSettings() {
guard let url = URL(string: UIApplication.openSettingsURLString),
UIApplication.shared.canOpenURL(url) else { return }
UIApplication.shared.open(url)
}Step 5 - Grant WebKit media permission (iOS 15+)
On iOS 15+, WebKit requires explicit handling of media capture permissions.
@available(iOS 15.0, *)
func webView(_ webView: WKWebView,
decideMediaCapturePermissionsFor origin: WKSecurityOrigin,
initiatedBy frame: WKFrameInfo,
type: WKMediaCaptureType) async -> WKPermissionDecision {
return .grant
}Recommended security restriction
Restrict permission to trusted domains:
if origin.host == "verify.socure.com" {
return .grant
}
return .denyNever auto-grant permissions for unknown origins.
Complete example ViewController
import UIKit
import WebKit
import AVFoundation
class ViewController: UIViewController, WKUIDelegate {
var webView: WKWebView?
override func viewDidLoad() {
super.viewDidLoad()
requestCameraPermission { [weak self] granted in
guard let self = self else { return }
DispatchQueue.main.async {
if granted {
self.setupWebView()
self.loadWebPage()
} else {
self.showPermissionAlert()
}
}
}
}
private func requestCameraPermission(completion: @escaping (Bool) -> Void) {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
completion(true)
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { granted in
completion(granted)
}
default:
completion(false)
}
}
private func setupWebView() {
let config = WKWebViewConfiguration()
let preferences = WKWebpagePreferences()
preferences.preferredContentMode = .mobile
config.defaultWebpagePreferences = preferences
config.allowsInlineMediaPlayback = true
let webView = WKWebView(frame: .zero, configuration: config)
webView.uiDelegate = self
self.webView = webView
view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: view.topAnchor),
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
private func loadWebPage() {
guard let webView = webView,
let url = URL(string: "https://YOUR_CAPTURE_URL") else { return }
webView.load(URLRequest(url: url))
}
private func showPermissionAlert() {
let alert = UIAlertController(
title: "Camera Access Required",
message: "Please enable camera access in Settings to continue.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { _ in
self.openAppSettings()
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
}
private func openAppSettings() {
guard let url = URL(string: UIApplication.openSettingsURLString),
UIApplication.shared.canOpenURL(url) else { return }
UIApplication.shared.open(url)
}
@available(iOS 15.0, *)
func webView(_ webView: WKWebView,
decideMediaCapturePermissionsFor origin: WKSecurityOrigin,
initiatedBy frame: WKFrameInfo,
type: WKMediaCaptureType) async -> WKPermissionDecision {
return .grant
}
}
Best Practices
- Restrict media permissions to trusted domains
- Persist evaluation identifiers before launching capture
- Validate redirect handling server-side
- Use in-app browsers for a seamless UX
Updated 13 days ago
