Question

MPMusicPlayerController setVolume is deprecated since iOS 7

Is there any other way to change system music volume? Preferably without user interaction. Its important feature: to increase volume automatically for any alarm clock from AppStore.

Was it helpful?

Solution

To answer you question exactly: Yes there is other way to change system volume without user interaction.

Until recent times I used to think that changing volume using MPVolumeView programmatically is possible only using private API. But I have just verified, that changing the value of volumeSlider and faking slider's touchUP event works:

MPVolumeView* volumeView = [[MPVolumeView alloc] init];

//find the volumeSlider
UISlider* volumeViewSlider = nil;
for (UIView *view in [volumeView subviews]){
    if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
        volumeViewSlider = (UISlider*)view;
        break;
    }
}

[volumeViewSlider setValue:1.0f animated:YES];
[volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];

(When slider receives touchUP event, it will invoke _commitVolumeChange method on itself, which will change the system volume)

OTHER TIPS

Until Apple sees fit to rescind this decision there are two remedies I have discovered:

  • Keep using the volume property, it is still working under iOS 7.0.2
  • Use AVAudioSession.outputVolume to read the volume when your app wakes and pop up an alert containing an MPVolumeView if the volume is lower than (or higher than) a user specified value. At least your user knows that their alarm (or whatever) will play quietly and has the opportunity to adjust the volume. Alternately you could just display very clearly the volume level so they get no surprises.

Just do this:

let masterVolumeSlider: MPVolumeView = MPVolumeView()

if let view = masterVolumeSlider.subviews.first as? UISlider{

    view.value = 1.0

}

@Hurden, I wrote a Swift code to implement the MPVolumeSlider:

for view in mpVolumeView.subviews {
  let uiview: UIView = view as UIView
  //println("\(uiview.description)")
  if uiview.description.rangesOfString("MPVolumeSlider").first != nil {
    mpVolumeSilder = (uiview as UISlider)
    currentDeviceVolume = mpVolumeSilder!.value
    return
  }
}

The func rangeOfString extension String can be found here Swift: A pure Swift method for returning ranges of a String instance (Xcode 6 Beta 5)

And use this code to let the gesture and mpvolumeslider work together

@IBAction func handlePan(recognizer: UIPanGestureRecognizer) {
  let translation = recognizer.translationInView(self.view)
  let dx = (translation.x-lastTranslationX)
  let volumeChanged = Float(dx / mpVolumeView.frame.width)
  currentDeviceVolume = currentDeviceVolume + Float(volumeChanged)

  if currentDeviceVolume > 1 {
    currentDeviceVolume = 1
  } else if currentDeviceVolume < 0 {
    currentDeviceVolume = 0
  }

  mpVolumeSilder!.value = currentDeviceVolume

  if recognizer.state == .Changed {
    lastTranslationX = translation.x
  }
  if recognizer.state == .Ended || recognizer.state == .Began {
    lastTranslationX = 0
  }
}

Apparently there is a way to change the system volume without displaying anything at all.

And best of all it works on iOS 11.

Here's how I've achieved it:

1) Create two variables in desired ViewController

let volumeView = MPVolumeView()
var slider: UISlider?

2) Add the code below into your viewDidLoad

volumeView.alpha = 0.01
self.view.addSubview(volumeView)

if let view = volumeView.subviews.first as? UISlider {
    slider = view
}

3) Change volue whenever you need

slider?.value = 0.4

Swift Version:

// Outlet added in Storyboard (Add UIView then set class to MPVolumeView)
@IBOutlet weak var mpVolumeView: MPVolumeView!

    // Get volume slider within MPVolumeView
    for subview in self.mpVolumeView.subviews {
        if (subview as UIView).description.rangeOfString("MPVolumeSlider") != nil {
            // Set volume
            let volumeSlider = subview as UISlider
            volumeSlider.value = 1

            // Works with or without the following line:
            // volumeSlider.sendActionsForControlEvents(UIControlEvents.TouchUpInside)
            break
        }
    }

This solution makes a bit nervous and I think its a bit odd an official API doesn't exist but here is my Swift solution built off of ambientlight's post

var _volumeView = MPVolumeView()
var _volumeSlider : UISlider? = nil



self.view.addSubview(_volumeView)
_volumeView.hidden = true

var i = 0
while i < _volumeView.subviews.count {
   if let _r = _volumeView.subviews[i] as? UISlider {
      _volumeSlider = _r
      break
   }
   ++i
}
let masterVolumeSlider  : MPVolumeView = MPVolumeView()

        if let view             = masterVolumeSlider.subviews.first as? UISlider{

        view.value              = fVolume!

        view.sendActionsForControlEvents(UIControlEvents.TouchUpInside)

        }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top