Detect iPhone Volume Button Up Press?
Asked Answered
S

4

13

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.

Soloman answered 12/3, 2012 at 0:48 Comment(0)
C
17

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

Cordeiro answered 14/4, 2012 at 16:14 Comment(2)
One thing I've noticed about using this method is that if I have another app playing music in the background (or potentially if you were already playing music in the app where you were doing this detection), that I would stop receiving notifications and the volume buttons would go back to just changing the volume of the music playing in the background =/Cervine
[[MPMusicPlayerController applicationMusicPlayer] volume] has been deprecated since iOS 7.Michaeu
J
37

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!");
    }
}
Jospeh answered 24/3, 2014 at 4:46 Comment(10)
I have looked at so many places over stackoverflow and this is the only one using non-deprecated methods and strings to get this done.Doublespace
The problem with this solution is that if the outputVolume is already max and they press the + button, the callback doesn't happen.Ramah
this solution is really nice, but ray is right :( is it possible to reset the value programatically to 0?Propane
How would I be able to have the action be called also when the volume is max?Butyraceous
@Butyraceous @donmarkusi You could try reading the current volume, then setting it back to (max - 1) if it's at the max. Look at UISlider and setValue:animated: to set it for UIControlEventTouchUpInside.Michaeu
Hey @JaredH, would it be possible to change the volume to a preset value if the user changes the volume up or down? I want that the volume stay in the same place no matter what the user does.Mallina
this is not fired when volume is full and press high volume key and same when no volume and press low volume key. is there any solution?Apriorism
this line is crucial: [audioSession setActive:YES error:nil]. without it you can't observe change.Agon
is it worked in iOS simulator ? is not working for me in simulator.Sentience
Don't forget to remove the observer with [audioSession removeObserver:self forKeyPath:@"outputVolume"]Ama
C
17

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

Cordeiro answered 14/4, 2012 at 16:14 Comment(2)
One thing I've noticed about using this method is that if I have another app playing music in the background (or potentially if you were already playing music in the app where you were doing this detection), that I would stop receiving notifications and the volume buttons would go back to just changing the volume of the music playing in the background =/Cervine
[[MPMusicPlayerController applicationMusicPlayer] volume] has been deprecated since iOS 7.Michaeu
F
3

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.

Falsecard answered 5/6, 2015 at 12:40 Comment(0)
T
1

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
  } 
}
Talanian answered 6/10, 2017 at 17:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.