Is there a notification that I can listen to that will tell me when an iPhone's volume is turned up?
I know about the AVSystemController_SystemVolumeDidChangeNotification
, but it is essential that the notification only be triggered when the volume has been turned up, not up or down.
Secondly, how can I hide the translucent view that appears when the volume up button is pressed, showing the system's volume? Camera+ has implemented this.
There is no documented way to to this, but you can use this workaround. Register for AVSystemController_SystemVolumeDidChangeNotification
notification and add an MPVolumeView
which will prevent the system volume view from showing up.
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:CGRectMake(-100, 0, 10, 0)];
[volumeView sizeToFit];
[self.view addSubview:volumeView];
And don't forget to start an Audio Session
AudioSessionInitialize(NULL, NULL, NULL, NULL);
AudioSessionSetActive(true);
In This case, the MPVolumeView
is hidden from the user.
As for checking if volume up or down was pressed, just grab the current application's volume
float volumeLevel = [[MPMusicPlayerController applicationMusicPlayer] volume];
and compare it with new volume after the button was pressed in notification callback
If you don't want to do it by yourself, there's a drop-in class available in github
https://github.com/blladnar/RBVolumeButtons
If you want an event you can register a listener on the "outputVolume" property:
- (void)viewWillAppear:(BOOL)animated {
AVAudioSession* audioSession = [AVAudioSession sharedInstance];
[audioSession setActive:YES error:nil];
[audioSession addObserver:self
forKeyPath:@"outputVolume"
options:0
context:nil];
}
-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqual:@"outputVolume"]) {
NSLog(@"volume changed!");
}
}
I solved this problem by adding own target/action for UISlider placed inside MPVolumeView
. So it's possible to catch volume change events and determine what button was pressed. Here's github repo with implementation of this approach.
It works fine with iOS 7 and above, no deprecation warnings and no rejection from Apple.
In order to distinguish volume action: INSTEAD OF (in observeValue guard)
temp != 0.5
USE for only volume up
temp > 0.5
and only detect volume down:
temp < 0.5
This solution below will print if either volume up or down are pressed.
import AVFoundation
import MediaPlayer
override func viewDidLoad() {
super.viewDidLoad()
let volumeView = MPVolumeView(frame: CGRect.zero)
for subview in volumeView.subviews {
if let button = subview as? UIButton {
button.setImage(nil, for: .normal)
button.isEnabled = false
button.sizeToFit()
}
}
UIApplication.shared.windows.first?.addSubview(volumeView)
UIApplication.shared.windows.first?.sendSubview(toBack: volumeView)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
AVAudioSession.sharedInstance().addObserver(self, forKeyPath: "outputVolume", options: NSKeyValueObservingOptions.new, context: nil)
do { try AVAudioSession.sharedInstance().setActive(true) }
catch { debugPrint("\(error)") }
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
AVAudioSession.sharedInstance().removeObserver(self, forKeyPath: "outputVolume")
do { try AVAudioSession.sharedInstance().setActive(false) }
catch { debugPrint("\(error)") }
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let key = keyPath else { return }
switch key {
case "outputVolume":
guard let dict = change, let temp = dict[NSKeyValueChangeKey.newKey] as? Float, temp != 0.5 else { return }
let systemSlider = MPVolumeView().subviews.first { (aView) -> Bool in
return NSStringFromClass(aView.classForCoder) == "MPVolumeSlider" ? true : false
} as? UISlider
systemSlider?.setValue(0.5, animated: false)
guard systemSlider != nil else { return }
debugPrint("Either volume button tapped.")
default:
break
}
}