Volume Shutter Buttons for Camera Capture

WWDC23 Platform State of the Union mentioned that Volume shutter buttons to trigger the camera shutter is coming later this year. This was mentioned at 0:30:15.

Would anyone know when this will be available?

Would anyone know when this will be available?

Well, I’m sure that someone knows (-:

Seriously though, AFAIK this hasn’t yet become available. It’s likely that a cool feature like this will end up being listed in the iOS & iPadOS Release Notes, so I recommend you keep an eye on those as we seed new OS releases.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Wish this was documented somewhere, when to expect..

I would really like to hear an official answer on this.

What more of an “official answer” are you expecting here? Folks from Apple can only discuss what’s been announced [1], and right now that doesn’t include a specific timeline or release vehicle for this feature.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] See tip 3 in Quinn’s Top Ten DevForums Tips.

Well, in this case, any sort of an answer...

It was very briefly announced in WWDC State Of the Union mentioning it will be out later this year.

It's almost December and Apple is prepping iOS 17.2 to be out in a few weeks, which is in 4th beta. So, it's safe to say that 17.2 is the last update for this year, and no mention of this feature.

I have been officially notified that this API was indeed in 17.2: AVCaptureEventInteraction

Found here: https://developer.apple.com/documentation/avkit/avcaptureeventinteraction

I am having a hard time figuring out the API.

 if #available(iOS 17.2, *) {
       let x = AVCaptureEventInteraction { event in
             print ("AVCaptureEventInteraction Fired")
       }
 }

What do I attach the interaction to?

I have been officially notified that this API was indeed in 17.2

Oh wow, look at that!

What do I attach the interaction to?

There’s some useful info about this in the doc comments in <AVKit/AVCaptureEventInteraction.h>. This isn’t really my field — and I mean both camera capture and UIKit! (-: — but those doc comments indicate that you need to attach the interaction to a view in the responder chain.

Let us know how you get along.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Oh wow, look at that!

Yes, this is why I was looking for an official answer on this. We do not have anywhere else to go for this.

Do comments section is the first place I went to. As you mentioned, it says that UIInteraction is used to register actions triggered by system capture events and should be attached to views in the responder chain. And the for handler, it says that it is called when either the primary or secondary events are triggered.

This is all that exsists on this. No more documentation or sample code. I will be experimenting and will update whatever I find.

This API is significant for developers of 3rd party camera apps, we had to go around many limitations to mimic this action.

Here is a full example (as a code snippet) that utilizes AVCaptureEventInteraction:

import UIKit
import AVFoundation
import AVKit

class ViewController: UIViewController {

    let previewView = PreviewView(frame: .zero)

    let photoOutput = AVCapturePhotoOutput()
    
    let session = AVCaptureSession()
    let sessionQueue = DispatchQueue(label: "Session Configuration")
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        previewView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(previewView)
        
        NSLayoutConstraint.activate([
            previewView.topAnchor.constraint(equalTo: view.topAnchor),
            previewView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            previewView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            previewView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
        ])
        
        sessionQueue.async { [self] in
            session.sessionPreset = .photo
            
            let input = try! AVCaptureDeviceInput(device: .default(for: .video)!)
            
            session.addInput(input)
            session.addOutput(photoOutput)
            
            session.startRunning()
            
            DispatchQueue.main.async { [self] in
                
                let interaction = AVCaptureEventInteraction { [self] event in
                    
                    print("Primary")
                    
                    switch event.phase {
                        
                    case .began:
                        print("began")
                    case .ended:
                        print("ended")
                        photoOutput.capturePhoto(with: .init(), delegate: self)
                    case .cancelled:
                        print("cancelled")
                    @unknown default:
                        print("unknown")
                    }
                } secondary: { [self] event in
                    
                    print("Secondary")
                    
                    switch event.phase {
                        
                    case .began:
                        print("began")
                    case .ended:
                        print("ended")
                        photoOutput.capturePhoto(with: .init(), delegate: self)
                    case .cancelled:
                        print("cancelled")
                    @unknown default:
                        print("unknown")
                    }
                }

                
                // By default, the interaction is not enabled, so enable it.
                interaction.isEnabled = true

                // Add the interaction to the preview view (which is in the responder chain).
                previewView.addInteraction(interaction)
                                
                previewView.previewLayer.session = session
            }
        }
    }
}

extension ViewController: AVCapturePhotoCaptureDelegate {
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        print("Captured:", photo)
    }
}


final class PreviewView: UIView {
    
    override class var layerClass: AnyClass {
        AVCaptureVideoPreviewLayer.self
    }
    
    var previewLayer: AVCaptureVideoPreviewLayer {
        layer as! AVCaptureVideoPreviewLayer
    }
}
Volume Shutter Buttons for Camera Capture
 
 
Q