iosvolume

iOS override hardware volume buttons (Same as Zello)


I am currently working on a PTT (push-to-talk) app. I am trying to use to hardware volume buttons to start/stop transmission.

All suggested solutions I've seen so far can be narrowed down to two:

  1. Using KVO to observe AVAudioSession property outputVolume.
  2. Using private API notification, namely AVSystemController_SystemVolumeDidChangeNotification and since iOS 15.0 - SystemVolumeDidChange.

Without getting into the pros and cons of each solution, they both have one thing in common - they are volume based, which raises several problems.

  1. Pressing volume buttons changes system volume. Although this can be fixed by resetting system volume, it's not a pretty solution.
  2. There is no way to discern between volume changes coming from hardware buttons, and volume changes coming from command center for instance, therefore buttons usage us limited to when app is in the foreground and active.
  3. When user presses volume button there is a short delay between first volume change event, and the consecutive events that follows, which makes it difficult to track quick press and release.

I have noticed that Zello app has somehow managed to overcome those issues, as they enable the usage of volume buttons even when device is closed or when command center is open - without any interference to the system volume. In addition changing volume from command center has not effect.

Does anyone have any idea as to how to achieve such a behavior?


Solution

  • Warning Private API usage follows. You MUST have some special circumstances that may allow you to get through AppStore Review.

    Take look at Explore the iOS SDK and use undocumented APIs from 2012.

    To summarise, you need to call the private method - [UIApplication setWantsVolumeButtonEvents:YES] to enable the following notifications:

    In Swift this can be enabled with something like:

    @objc public protocol UIApplicationPrivate {
        @objc func setWantsVolumeButtonEvents(_:Bool)
    }
    
    class VolumeButtonsManager {
        private static var observer: NSObjectProtocol?
    
        static func setup(with application: UIApplication) {
            observer = NotificationCenter.default.addObserver(forName: nil,
                                                              object: nil,
                                                              queue: nil,
                                                              using: handleEvent)
            
            let application = unsafeBitCast(application, to:UIApplicationPrivate.self)
            application.setWantsVolumeButtonEvents(true)
        }
    
        private static func handleEvent(_ notification: Notification) {
            switch notification.name.rawValue {
            case "_UIApplicationVolumeUpButtonDownNotification": print("Volume Up Button Down")
            case "_UIApplicationVolumeUpButtonUpNotification": print("Volume Up Button Up")
            case "_UIApplicationVolumeDownButtonDownNotification": print("Volume Down Button Down")
            case "_UIApplicationVolumeDownButtonUpNotification": print("Volume Down Button Up")
            default: break
            }
        }
    }
    

    UIApplicationPrivate via Silencing a warning for explicitly constructed selector.