I was also struggling to solve the task of handling volume buttons press event, aiming to
- get the event
- recognise if it is volume up or down
- be able to react even at system max/min level
- handle the event (in my case, executing the js callback in WKWebView)
- have it working on both iOS 15 / iOS below 15.
Final solution which works for me (as per sep-2022) is below:
In my view controller
var lastVolumeNotificationSequenceNumber: Int? //see below explanations - avoiding duplicate events
var currentVolume: Float? //needed to remember your current volume, to properly react on up/down events
In my loadView func:
if #available(iOS 15, *) {
NotificationCenter.default.addObserver(self, selector: #selector(volumeChanged(_:)), name: NSNotification.Name(rawValue: "SystemVolumeDidChange"), object: nil)
}
else {
NotificationCenter.default.addObserver(self, selector: #selector(volumeChanged(_:)), name: NSNotification.Name(rawValue: "AVSystemController_SystemVolumeDidChangeNotification"), object: nil)
}
#available tag allows you to choose the notification set up according to iOS version.
...And my view controller has this:
@objc func volumeChanged(_ notification: NSNotification) {
DispatchQueue.main.async { [self] in
if #available(iOS 15, *) {
volumeControlIOS15(notification)
}
else {
volumeControlIOS14(notification)
}
}
}
This one is to handle the event itself and to distinguish the code for 14/15 versions (slightly different)
Please note: DispatchQueue.main.async used here, as soon as completion handler is (as stated above) NOT on the main thread, and in my case it HAS to be. I had some crashes and thread warnings before I figured it out.
func manageVolume(volume: Float, minVolume: Float) {
switch volume {
case minVolume: do {
currentVolume = minVolume + 0.0625
}
case 1: do {
currentVolume = 0.9375
}
default: break
}
if let cV = currentVolume {
if volume > cV {
//do your stuff here
}
if volume < cV {
//do your stuff here
}
currentVolume = volume
}
else {
currentVolume = volume
}
}
This function is to handle the volume button press event, and also helps you to a) understand if the event is "up" or "down", and b) manage the case of reaching max/min value, should you need to proceed with event handling even when you're touching the max/min (this is done by simply reducing/increasing current volume variable - remember, you can't change the system volume itself, however, the variable is all yours ;) )
func volumeControlIOS15(_ notification: NSNotification) {
let minVolume: Float = 0.0625
if let volume = notification.userInfo!["Volume"] as? Float {
//avoiding duplicate events if same ID notification was generated
if let seqN = self.lastVolumeNotificationSequenceNumber {
if seqN == notification.userInfo!["SequenceNumber"] as! Int {
NSLog("Duplicate nofification received")
}
else {
self.lastVolumeNotificationSequenceNumber = (notification.userInfo!["SequenceNumber"] as! Int)
manageVolume(volume: volume, minVolume: minVolume)
}
}
else {
self.lastVolumeNotificationSequenceNumber = (notification.userInfo!["SequenceNumber"] as! Int)
manageVolume(volume: volume, minVolume: minVolume)
}
}
}
It is the main iOS 15 implementation func. As you can see, minVolume is not a number, it's a let constant - and it's different from iOS 14 (I found on iOS 14 it is 0, while iOS 15 is not going below 0.0625 on my physical device - please don't ask me why, it's a mystery ;))
It is also handling the last notification unique ID and omitting duplicated notification events, which are (somehow) quite common with iOS15.
func volumeControlIOS14(_ notification: NSNotification) {
//old implementation for iOS < 15
let minVolume: Float = 0
if let volume = notification.userInfo!["AVSystemController_AudioVolumeNotificationParameter"] as? Float {
manageVolume(volume: volume, minVolume: minVolume)
}
}
Same here for iOS 14, with 3 main differences: a) notification UserInfo key, as stated above, is different b) no duplicate notifications control - as soon as there are no duplicates I ever observed on iOS 14, and c) minVolume is 0, which is correct for iOS 14
Hope it is helpful :)