Skip to content

Instantly share code, notes, and snippets.

@christianselig
Created August 12, 2025 18:02
Show Gist options
  • Save christianselig/350524e85be962974f0b07eb852dc68b to your computer and use it in GitHub Desktop.
Save christianselig/350524e85be962974f0b07eb852dc68b to your computer and use it in GitHub Desktop.
import UIKit
import AVFoundation
class ViewController: UIViewController, AVCapturePhotoCaptureDelegate {
var captureSession: AVCaptureSession!
var photoOutput: AVCapturePhotoOutput!
var previewLayer: AVCaptureVideoPreviewLayer!
var captureButton: UIButton!
var captureStartTime: Date?
override func viewDidLoad() {
super.viewDidLoad()
setupCameraSession()
setupPreviewLayer()
setupCaptureButton()
}
func setupCameraSession() {
captureSession = AVCaptureSession()
captureSession.sessionPreset = .photo
let wideCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)!
let input = try! AVCaptureDeviceInput(device: wideCamera)
captureSession.addInput(input)
photoOutput = AVCapturePhotoOutput()
let prewarmSettings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
prewarmSettings.photoQualityPrioritization = .speed
photoOutput.setPreparedPhotoSettingsArray([prewarmSettings]) { didCompletePrewarming, error in
print("Prewarming complete: \(didCompletePrewarming) with error: \(String(describing: error))")
}
if captureSession.canAddOutput(photoOutput) {
captureSession.addOutput(photoOutput)
}
captureSession.startRunning()
}
func setupPreviewLayer() {
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
}
func setupCaptureButton() {
captureButton = UIButton(type: .system)
captureButton.setTitle("Capture", for: .normal)
captureButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: .largeTitle)
captureButton.backgroundColor = UIColor.systemBlue
captureButton.setTitleColor(.white, for: .normal)
captureButton.translatesAutoresizingMaskIntoConstraints = false
captureButton.addTarget(self, action: #selector(capturePhoto), for: .touchUpInside)
view.addSubview(captureButton)
NSLayoutConstraint.activate([
captureButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
captureButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
}
@objc func capturePhoto() {
captureStartTime = Date.now
let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
settings.photoQualityPrioritization = .speed
photoOutput.capturePhoto(with: settings, delegate: self)
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
let callbackTime = Date.now
let elapsedTime = callbackTime.timeIntervalSince(captureStartTime!)
let image = UIImage(data: photo.fileDataRepresentation()!)!
print("Time from button tap to delegate callback: \(elapsedTime * 1000) ms. Image size: \(image.size)")
captureStartTime = nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment