Episode #304
Adding a Flash Toggle Camera Control
Series: Camera Capture and Detection

Episode #304
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
}
}
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
}
}
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
}
}
This episode uses Xcode 9.0-beta6, Swift 4.