iOS Webview Integration

Embed the Capture App in a native iOS App

The implementation:

How it works

  1. App screen loads.
  2. Camera permission is checked.
  3. If granted:
    • WebView is initialized.
    • Capture URL is loaded.
  4. If denied:
    • User is directed to enable permission in Settings.
  5. Web capture experience runs within the app.

Requirements

Minimum platform

Required frameworks

Swift

import UIKit
import WebKit
import AVFoundation

Required Info.plist Key

Add the following key:

Text

NSCameraUsageDescription

Example:

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.

Swift

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

Step 2 - Configure WKWebView

Configure the WebView for mobile rendering and inline media playback.

Swift

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

Step 3 - Load the Capture App URL

Swift

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

Step 4 - Handle denied permission

If camera access is denied, direct the user to Settings.

Swift

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.

Swift

@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:

Swift

if origin.host == "verify.socure.com" {
    return .grant
}
return .deny

Never auto-grant permissions for unknown origins.

Complete example ViewController

Swift

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