Build a full-screen camera with gesture controls in iOS using AVCaptureSession with AVCapturePhotoOutput from the AVFoundation framework. AVCaptureSession coordinates data flow between the camera device input and the photo output. AVCaptureVideoPreviewLayer displays the live feed. UISwipeGestureRecognizer objects detect swipe-up for camera switching and swipe-left or swipe-right for digital zoom. AVCapturePhotoOutput captures full-resolution still images, and UIImageWriteToSavedPhotosAlbum saves them to the camera roll.
Configure AVCaptureSession with Front and Back Cameras
Create an AVCaptureSession with the .photo preset. Use AVCaptureDevice.DiscoverySession to enumerate both front and back cameras.
let session = AVCaptureSession()
session.sessionPreset = .photo
let devices = AVCaptureDevice.DiscoverySession(
deviceTypes: [.builtInWideAngleCamera],
mediaType: .video,
position: .unspecified
).devices
var backCamera: AVCaptureDevice?
var frontCamera: AVCaptureDevice?
for device in devices {
if device.position == .back { backCamera = device }
if device.position == .front { frontCamera = device }
}
The .unspecified position returns all cameras. Sort them by the position property to assign backCamera and frontCamera separately.
Set Up the Camera Input and Photo Output
Wrap the initial device in an AVCaptureDeviceInput and attach it to the session with the photo output.
guard let currentDevice = backCamera,
let input = try? AVCaptureDeviceInput(device: currentDevice) else { return }
let photoOutput = AVCapturePhotoOutput()
session.addInput(input)
session.addOutput(photoOutput)
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.frame = view.bounds
view.layer.addSublayer(previewLayer)
DispatchQueue.global(qos: .background).async {
session.startRunning()
}
Session startRunning() blocks the calling thread. Run it on a background queue. Add the preview layer as a sublayer before adding gesture recognizers and buttons so they render on top.
Capture a Still Image with AVCapturePhotoOutput
Create an AVCapturePhotoSettings with JPEG encoding and call capturePhoto(with:delegate:).
@IBAction func capturePhoto(_ sender: UIButton) {
let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
settings.flashMode = .auto
photoOutput.capturePhoto(with: settings, delegate: self)
}
extension CameraViewController: AVCapturePhotoCaptureDelegate {
func photoOutput(_ output: AVCapturePhotoOutput,
didFinishProcessingPhoto photo: AVCapturePhoto,
error: Error?) {
guard let data = photo.fileDataRepresentation(),
let image = UIImage(data: data) else { return }
// Show preview or save
}
}
AVCapturePhotoSettings supports flash mode, red-eye reduction, image stabilization, and RAW capture on compatible devices. fileDataRepresentation() returns image data with orientation metadata included.
Save Photos to the Camera Roll
Use UIImageWriteToSavedPhotosAlbum to write the image to the Photos library.
@IBAction func saveToLibrary(_ sender: UIButton) {
guard let image = capturedImage else { return }
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
dismiss(animated: true)
}
Add NSPhotoLibraryAddUsageDescription to Info.plist with a reason such as "To save captured photos to your library." Without this key, the save operation fails silently.
Implement Swipe-Up Gesture to Toggle Cameras
Add a UISwipeGestureRecognizer configured for the .up direction.
let swipeUp = UISwipeGestureRecognizer(target: self, action: #selector(switchCamera))
swipeUp.direction = .up
view.addGestureRecognizer(swipeUp)
@objc func switchCamera() {
session.beginConfiguration()
let newDevice: AVCaptureDevice? = (currentDevice == backCamera) ? frontCamera : backCamera
guard let device = newDevice else { return }
for input in session.inputs {
session.removeInput(input)
}
guard let newInput = try? AVCaptureDeviceInput(device: device) else { return }
if session.canAddInput(newInput) {
session.addInput(newInput)
}
currentDevice = device
session.commitConfiguration()
}
beginConfiguration() and commitConfiguration() wrap the input swap in an atomic transaction. This prevents visual artifacts during the camera transition.
Implement Swipe Gestures for Digital Zoom
Add UISwipeGestureRecognizer objects for .right (zoom in) and .left (zoom out) directions.
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(zoomIn))
swipeRight.direction = .right
view.addGestureRecognizer(swipeRight)
let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(zoomOut))
swipeLeft.direction = .left
view.addGestureRecognizer(swipeLeft)
@objc func zoomIn() {
guard let device = currentDevice else { return }
let maxZoom = device.maxAvailableVideoZoomFactor
let newZoom = min(device.videoZoomFactor + 1.0, maxZoom)
applyZoom(newZoom)
}
@objc func zoomOut() {
guard let device = currentDevice else { return }
let newZoom = max(device.videoZoomFactor - 1.0, 1.0)
applyZoom(newZoom)
}
func applyZoom(_ factor: CGFloat) {
guard let device = currentDevice else { return }
do {
try device.lockForConfiguration()
device.ramp(toVideoZoomFactor: factor, withRate: 1.0)
device.unlockForConfiguration()
} catch {
print("Zoom failed: (error)")
}
}
videoZoomFactor ranges from 1.0 to the device's maximum, typically 5.0x on iPhones. ramp(toVideoZoomFactor:withRate:) provides a smooth animated transition. lockForConfiguration() is required before modifying zoom properties.
Request Camera and Photo Library Permissions
Add NSCameraUsageDescription and NSPhotoLibraryAddUsageDescription to Info.plist. Both are required for camera apps targeting iOS 10 and later.
Best Practices for Custom Camera Apps
Run AVCaptureSession startRunning() and stopRunning() on background queues. Use beginConfiguration() and commitConfiguration() for all session changes such as switching cameras or changing presets. Check device capabilities with hasFlash, isTorchAvailable, and maxAvailableVideoZoomFactor before using them. Downsample preview images to avoid memory pressure from full-resolution photos. Provide visual shutter feedback with a brief white flash overlay or an animation on the capture button. Add a focus rectangle by implementing a UITapGestureRecognizer that calls AVCaptureDevice.videoZoomFactor and focusPointOfInterest. Handle the front camera mirroring by setting videoMirrored on the AVCaptureVideoPreviewLayer connection.
Summary
This tutorial covered the complete implementation process from setup to working code. You now have a functional implementation that you can extend and customize for your own projects. The same patterns apply to related features in the iOS ecosystem.
Frequently Asked Questions
What iOS version is required? This tutorial targets iOS 10. Most of the APIs shown work on earlier versions as well, but some newer APIs may require the version specified in the article.
What are the prerequisites for this tutorial? You need Xcode installed on a Mac and basic familiarity with Swift programming. No prior experience with the specific framework is required.