iosiphoneios11avcaptureshutter

How do I implement a volume shutter in iOS 11?


I want to implement a volume shutter in my camera app. When the user presses the volume button, I should get an event to take a photo.

I'm looking for an implementation that meets the following requirements:

Other questions and answers exist on this topic, but for older versions of iOS, so I wanted to find one that works on iOS 11.

Camera apps like ProCamera, ProCam and Camera+ have the volume shutter that satisfies all these conditions, so it's clearly possible.


Solution

  • So here is the code that will meet all your requirements – I'm not sure whether or not Apple will approve this though.
    I've pulled all this code from questions/answers here on StackOverflow.

    Tested with iOS 10.2 in Xcode 8.3.1

    You need to use the AVFoundation and MediaPlayer frameworks for this to work.

    import UIKit
    import AVFoundation
    import MediaPlayer
    
    class ViewController: UIViewController {
    
        //keeps track of the initial volume the user had set when entering the app
        //used to reset the volume when he exits the app
        var volume: Float = 0
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let audioSession = AVAudioSession.sharedInstance()
            volume = audioSession.outputVolume-0.1 //if the user is at 1 (full volume)
            try! audioSession.setActive(true)
            audioSession.addObserver(self, forKeyPath: "outputVolume", options: NSKeyValueObservingOptions.new, context: nil)
            //prevents the volume hud from showing up
            let volView = MPVolumeView(frame: .zero)
            view.addSubview(volView)
        }
    
        override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            //when the user changes the volume,
            //prevent the output volume from changing by setting it to the default volume we specified,
            //so that we can continue pressing the buttons for ever
            (MPVolumeView().subviews.filter{NSStringFromClass($0.classForCoder) == "MPVolumeSlider"}.first as? UISlider)?.setValue(volume, animated: false)
    
            //implement your photo-capturing function here
            print("volume changed")
        }
    }
    

    Update

    If you want to make sure your code is still working after the user exits the app, use the AppDelegate to install the observer when the app becomes active, like this:

    AppDelegate

    import UIKit
    import AVFoundation
    import MediaPlayer
    
    var volume: Float = 0.5
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        var window: UIWindow?
        let audioSession = AVAudioSession.sharedInstance()
    
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
            return true
        }
    
        override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            (MPVolumeView().subviews.filter{NSStringFromClass($0.classForCoder) == "MPVolumeSlider"}.first as? UISlider)?.setValue(volume, animated: false)
    
            NotificationCenter.default.removeObserver(self)
            NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "volumeChanged")))
        }
    
        func applicationDidBecomeActive(_ application: UIApplication) {
            volume = audioSession.outputVolume
            if volume == 0 { volume += 0.1 } else if volume == 1 { volume -= 0.1 }
            try! audioSession.setActive(true)
            audioSession.addObserver(self, forKeyPath: "outputVolume", options: NSKeyValueObservingOptions.new, context: nil)
        }
    
        func applicationWillResignActive(_ application: UIApplication) {
            audioSession.removeObserver(self, forKeyPath: "outputVolume")
        }
    }
    

    ViewController

    import UIKit
    import AVFoundation
    import MediaPlayer
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            NotificationCenter.default.addObserver(self, selector: #selector(self.volumeChanged), name: Notification.Name(rawValue: "volumeChanged"), object: nil)
            //prevents the volume hud from showing up
            let volView = MPVolumeView(frame: .zero)
            view.addSubview(volView)
        }
    
        func volumeChanged() {
            print("volume changed")
        }
    }