Episode #300

Camera Sounds and UI

Series: Camera Capture and Detection

12 minutes
Published on August 25, 2017

This video is only available to subscribers. Get access to this video and 586 others.

In this episode we implement features that make the app feel more like a camera. When tapping the screen, we play the system camera shutter sound using AudioServices, then we add a small but useful flash effect to reinforce the fact the user took a photo. We'll also talk about a strategy for capturing the image by using a flag.

Episode Links

Tapping to take a photo

To take a photo, we will use a UITapGestureRecognizer on the entire screen.

   override func viewDidLoad() {
        super.viewDidLoad()

        let tap = UITapGestureRecognizer(target: self, action: #selector(onTap(_:)))
        view.addGestureRecognizer(tap)
    }
    @objc func onTap(_ tap: UITapGestureRecognizer) {
        // play shutter sound

        // stop detecting new rectangles        

        // flash

        // save photo
    }

Playing the Shutter Sound

To play the system camera shutter sound, we need to know the ID. Using the wiki link above, we found that this is 1108. We'll start by adding a constant at the top of the class:

let cameraShutterSoundID: SystemSoundID = 1108

Then we use AudioServices to play the sound:

    @objc func onTap(_ tap: UITapGestureRecognizer) {
        // play shutter sound
        AudioServicesPlaySystemSound(cameraShutterSoundID)

        // ...
    }

Displaying the Flash

For this we'll create a full screen white layer and animate its opacity using Core Animation.

    private func flashScreen() {
        let flash = CALayer()
        flash.frame = view.bounds
        flash.backgroundColor = UIColor.white.cgColor
        view.layer.addSublayer(flash)
        flash.opacity = 0

        let anim = CABasicAnimation(keyPath: "opacity")
        anim.fromValue = 0
        anim.toValue = 1
        anim.duration = 0.1
        anim.autoreverses = true
        anim.isRemovedOnCompletion = true

        flash.add(anim, forKey: "flashAnimation")

    }

We need to make sure to remove this layer when we're done, so we implement the animation delegate method in an extension. To do this we'll need to set ourselves up as the delegate, conform to the protocol, and stash away the created layer in a property so we can clean it up later.

    var flashLayer: CALayer?

   // in flashScreen()
   anim.delegate = self

   self.flashLayer = flash
extension CaptureViewController : CAAnimationDelegate {
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        flashLayer?.removeFromSuperlayer()
        flashLayer = nil
        detectRectangles = true
    }
}

Finally we need to call this right after we play the sound:

    @objc func onTap(_ tap: UITapGestureRecognizer) {
        // play shutter sound
        AudioServicesPlaySystemSound(cameraShutterSoundID)

        // stop detecting rectangles

        // flash
        flashScreen()
    }

This episode uses Xcode 9.0-beta3, Swift 4.