In this episode we extract our camera preview layer into its own view so we can add subviews. We’ll use this to add a flash button control to the UI, which will require us to learn about locking the device and controlling the camera’s "torch".
Links Designing Icons with Sketch - Watch the episode where we design this flash AVCaptureDevice Documentation Creating a Flash Control For the flash button I want some behavior to be encapsulated in a separate class so we don't muddy up our view controller. This button class will be responsible for controlling how large the tap area of the button should be (not allowing the image to drive how big the button is) as well as toggling between the 2 flash icons. class FlashButton : UIButton { let TapSize = 50 override class var requiresConstraintBasedLayout: Bool { return true } init() { super.init(frame: CGRect(x: 0, y: 0, width: TapSize, height: TapSize)) tintColor = .white translatesAutoresizingMaskIntoConstraints = false adjustsImageWhenHighlighted = false setImage(#imageLiteral(resourceName: "Flash"), for: .normal) setImage(#imageLiteral(resourceName: "NoFlash"), for: .selected) imageView?.contentMode = .center addTarget(self, action: #selector(onFlashTapped), for: .touchUpInside) } override var intrinsicContentSize: CGSize { return CGSize(width: TapSize, height: TapSize) } required init?(coder aDecoder: NSCoder) { fatalError() } @objc func onFlashTapped() { isSelected = !isSelected } } Moving Our Camera Preview Layer Into a New Class By moving this preview layer as a sublayer of a different view's layer, we can once again properly add subviews to our main view controller's view. This will be useful for drawing the flash button on top. class CameraView : UIView { let previewLayer: AVCaptureVideoPreviewLayer! init(captureSession: AVCaptureSession) { previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.backgroundColor = UIColor.black.cgColor previewLayer.videoGravity = .resizeAspect super.init(frame: UIScreen.main.bounds) layer.addSublayer(previewLayer) setNeedsLayout() layoutIfNeeded() } required init?(coder aDecoder: NSCoder) { fatalError() } override func layoutSubviews() { super.layoutSubviews() previewLayer.frame = layer.bounds } } Controlling the Torch Before we can turn the camera's torch on or off we have to acquire a lock. This call can throw (if the device is busy or another application currently has the lock) so we'll create a quick helper function that will take care of this for us: func withDeviceLock(on device: AVCaptureDevice, block: (AVCaptureDevice) -> Void) { do { try device.lockForConfiguration() block(device) device.unlockForConfiguration() } catch { // can't acquire lock } } To turn the torch on: func turnOnTorch(device: AVCaptureDevice) { guard device.hasTorch else { return } withDeviceLock(on: device) { try? $0.setTorchModeOn(level: AVCaptureDevice.maxAvailableTorchLevel) } } And to turn it off: func turnOffTorch(device: AVCaptureDevice) { guard device.hasTorch else { return } withDeviceLock(on: device) { $0.torchMode = .off } }