Sample Code

Learn how to implement Lens in an iOS application

Initialization & Enrollment - Application Delegate

We recommend initializing Lens only once per application launch, and since enrollment can take several seconds to complete (depending on connection), Lens should be initialized before presenting the camera. For these reasons, we recommend that you initialize Lens in your application delegate or in a view controller or other object which will be around for the lifecycle of your application

Call the Lens designated initializer, passing your API Key. Since this call may throw, it should be wrapped in a do/try/catch block:

import UIKit
import LensSDK

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
  	var lens: Lens?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        // Initiates the enrollment process with your API key. It may take a few
        // seconds, so it's best to do so early in your app's startup sequence.
        do {
            let lens = try Lens(with: "API_KEY")
            self.lens = lens
      
          	// Use dependency injection to pass the camera to the camera controller.
            if let cameraController = window?.rootViewController as? CameraViewController {
                cameraController.camera = lens.camera
            }
        }
        catch {
            print("\(error.localizedDescription)")
        } 

        return true
    }

    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
        // LensCamera uses background URLSession tasks, and this completion
        // handler is required to be set.
        LensCamera.shared?.activityCompletionHandler = completionHandler
    }
}

Alternatively, consider SomeParentViewController which is created early in the application's lifecycle and will be in memory for as long as the application is running:

import UIKit
import LensSDK

class SomeParentViewController: UIViewController {
    private var lens: Lens?
  
    override func viewDidLoad {
        super.viewDidLoad()
      
        do {
            let lens = try Lens(with: "API_KEY")
            self.lens = lens
        }
        catch {
            print("\(error.localizedDescription)")
        }
    }
  
    @IBAction func openCamera() {
        let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
        
        guard let vc = mainStoryboard.instantiateViewController(withIdentifier: "CameraViewController") as? CameraViewController else { return }
        vc.camera = self.lens?.camera

        navigationController?.pushViewController(vc, animated: true)
    }
}

Example Camera Implementation

import UIKit
import LensSDK
import Dispatch

class CameraViewController: UIViewController, LensCameraDelegate {
    // `cameraPreviewView` is a subview of the view controller's view, and is passed to Lens
    // in the `configureSession` call. It will host Lens' camera preview.
    // The view should extend to the top and bottom safe area layout guides.
    // Camera controls should be sibling views to `cameraPreviewView`.
    @IBOutlet weak var cameraPreviewView: UIView!

    // A button whose action will trigger photo capture.
    @IBOutlet weak var captureButton: UIButton!

    // A label which shows how long the current video has been recording.
    @IBOutlet weak var durationLabel: UILabel!

    // A reference to the Lens camera object.
    private var camera: LensCamera?

    // A custom UIView which acts as a visual aid to focus operations.
    // FocusReticleView is a custom view, and is not part of the Lens SDK.
    private let focusReticle = FocusReticleView()
    private let reticleSize: CGFloat = 132

    // DispatchSourceTimer is preferred over Timer because Timer requires a
    // run loop and delivers its events with some latency.
    private var timer: DispatchSourceTimer?
    private let timerInterval = DispatchTimeInterval.seconds(1)
    private let timerQueue = DispatchQueue(label: "com.yourcompany.videotimer")
    private var durationInSeconds: Int = 0 {
        didSet {
            updateDurationLabel()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationController?.isNavigationBarHidden = true
    }
  
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        guard let camera = self.camera else { return }
        // pass the previewView and delegate to LensCamera
        camera.configureSession(previewView: cameraPreviewView, delegate: self))
        // tell LensCamera to start the AVCapture session
        camera.startCaptureSession()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        camera?.stopCaptureSession()
        stopTimer()
    }

    deinit {
        timer?.cancel()
    }

    // MARK: - IBAction
    @IBAction func captureButtonAction(button: UIButton) {
        guard let camera = self.camera else {
            print("Unable to find LensCamera instance.")
            return
        }
        switch lens.cameraMode {
            case .photo: self.takePhoto()
            case .video: self.recordVideo()
        }
    }

    @IBAction func toggleMode(button: UIButton) {
        guard let camera = self.camera else {
            print("Unable to find LensCamera instance.")
            return
        }
        switch camera.cameraMode {
            case .photo: camera.cameraMode = .video
            case .video: camera.cameraMode = .photo
        }
    }

    @IBAction func switchCamera(button: UIButton) {
        guard let camera = self.camera else {
            print("Unable to find LensCamera instance.")
            return
        }
        camera.changeCamera()
    }

    @IBAction func toggleFlash(button: UIButton) {
        guard camera = self.camera else {
            print("Unable to find LensCamera instance.")
            return
        }
        camera.toggleFlash()
    }

    // MARK: - UIGestureRecognizer
    @objc func handleTapToFocus(_ sender: UITapGestureRecognizer) {
        guard let camera = self.camera else {
            print("Unable to find LensCamera instance.")
            return
        }
        focusReticle.removeFromSuperview()
        let focusPoint = sender.location(in: cameraPreviewView)
        self.cameraPreviewView.addSubview(focusReticle)
        focusReticle.frame = CGRect(x: focusPoint.x - reticleSize/2,
                                    y: focusPoint.y - reticleSize/2,
                                    width: reticleSize,
                                    height: reticleSize)
        focusReticle.animateReticle(at: focusPoint)
        camera.focus(with: sender)
    }

    @objc func handleZoom(_ sender: UIPinchGestureRecognizer) {
        guard let camera = self.camera else {
            print("Unable to find LensCamera instance.")
            return
        }
        camera.zoom(with: sender)
    }

    // MARK: - private methods
    private func takePhoto() {
        takePhotoAnimation()
        self.camera?.beginCapture()
    }

    private func takePhotoAnimation() {
        DispatchQueue.main.async { [weak self] in
            guard let preview = self?.cameraPreviewView else { return }
            preview.layer.opacity = 0
            UIView.animate(withDuration: 0.30) {
                preview.layer.opacity = 1
            }
        }
    }

    private func updateDurationLabel() {
        let minutes = durationInSeconds / 60 % 60
        let seconds = durationInSeconds % 60
        let durationString = String(format: "%02d:%02d", minutes, seconds)
        // Updating the UI needs to happen on the main thread
        DispatchQueue.main.async {
            self.durationLabel.text = durationString
        }
    }

    private func startTimer() {
        timer = DispatchSource.makeTimerSource(flags: [], queue: timerQueue)
        timer?.setEventHandler(handler: { [weak self] in
            self?.durationInSeconds += 1
        })
        timer?.schedule(deadline: .now() + .seconds(1), repeating: .seconds(1), leeway: .nanoseconds(0))
        timer?.resume()
    }

    private func stopTimer() {
        timer?.cancel()
        durationInSeconds = 0
    }

    private func recordVideo() {
        guard let camera = self.camera else { return }
        is camera.isRecordingVideo {
            camera.stopVideoCapture()
        } else {
            camera.startVideoCapture()
        }
    }

		// MARK: - LensCameraDelegate
    func lensCameraDidFailWithError(_ error: LensCameraError) {
        // handle the error appropriately
    }

		// MARK: - LensCameraDelegate - Photo capture
    func lensCameraDidGeneratePhotoThumbnail(_ imageName: String, image: UIImage) {
        // add thumbnail to a gallery
    }

    func lensCameraDidGenerateTruepic(_ imageName: String, image: Data) {
        // save photo to host app's gallery or upload to host's server.
      	// Saving to the user's Photo Library will result in a loss of C2PA assertion and claim metadata.
        // Converting the returned image data to a `UIImage` will result in a loss of C2PA assertion and claim metadata. 
      	// `UIImage` does not support the features generated during image signing.
    }

		// MARK: - LensCameraDelegate - Video capture
    func lensCameraDidStartRecordingVideo() {
        self.startTimer()
        // Other UI updates, dispatched to the main thread.
    }

    func lensCameraDidStopRecordingVideo() {
        self.stopTimer()
        // Other UI updates, dispatched to the main thread.
    }

    func lensCameraVideoRecordingFailed(error: Error?) {
        // Present a warning to the user.
    }

    func lensCameraDidGenerateVideoThumbnail(fileName: String, image: UIImage, duration: TimeInterval?) {
        // Add the thumbnail to a gallery.
    }

    func lensCameraDidGenerateTruepicVideo(_ url: URL) {
        // Upload the signed video to a host server.
    }
}