16

I want to change the device volume on iOS (iphone).

I know that i can change the volume of music library with this lines below:

//implement at first MediaPlayer framework
MPMusicPlayerController *musicPlayer = [MPMusicPlayerController iPodMusicPlayer];
musicPlayer.volume = 1;

But thats not my aim.
I want to change the device volume or let me say the volume of ringer.

How can i do that? just change the DEVICE volume?

Daniel Compton
  • 13,878
  • 4
  • 40
  • 60
brush51
  • 5,691
  • 6
  • 39
  • 73

9 Answers9

19

To answer brush51's question:

How can i do that? just change the DEVICE volume?

As 0x7fffffff suggested:

You cannot change device volume programatically, however MPVolumeView (volume slider) is there to change device volume but only through user interaction.

So, Apple recommends using MPVolumeView, so I came up with this:

Add volumeSlider property:

@property (nonatomic, strong) UISlider *volumeSlider;

Init MPVolumeView and add somewhere to your view (can be hidden, without frame, or empty because of showsRouteButton = NO and showsVolumeSlider = NO):

MPVolumeView *volumeView = [MPVolumeView new];
volumeView.showsRouteButton = NO;
volumeView.showsVolumeSlider = NO;
[self.view addSubview:volumeView];

Find and save reference to UISlider:

__weak __typeof(self)weakSelf = self;
[[volumeView subviews] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if ([obj isKindOfClass:[UISlider class]]) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        strongSelf.volumeSlider = obj;
        *stop = YES;
    }
}];

Add target action for UIControlEventValueChanged:

[self.volumeSlider addTarget:self action:@selector(handleVolumeChanged:) forControlEvents:UIControlEventValueChanged];

And then detect volume changing (i.e. by the hardware volume controls):

- (void)handleVolumeChanged:(id)sender
{
    NSLog(@"%s - %f", __PRETTY_FUNCTION__, self.volumeSlider.value);
}

and also other way around, you can set volume by:

self.volumeSlider.value = < some value between 0.0f and 1.0f >;

Hope this helps (and that Apple doesn't remove MPVolumeSlider from MPVolumeView).

msrdjan
  • 815
  • 9
  • 8
  • 1
    did you ever test that? Did you become rejected for changing volume programatically? – iCaramba Jun 06 '14 at 07:07
  • 1
    Doesn't work for me on ios 7.1 and 8. Value of the slider changes, but the system sound volume does not. – Dvole Oct 23 '14 at 04:25
  • @msrdjan: as I check in ios 8.3, volume value won't be setted if `volumeView.showsRouteButton = NO; volumeView.showsVolumeSlider = NO;` should keep default as YES – nahung89 Aug 13 '15 at 04:41
  • 1
    in my tests this only worked when playing a sound at the same time continously and first setting the volume to more than 0 and afterwards reducing to 0 - just for reference for somebody running into the same issue – Christian Jan 05 '16 at 10:36
8

Here's what I've done:

func setSystemVolume(volume: Float) {
    let volumeView = MPVolumeView()

    for view in volumeView.subviews {
        if (NSStringFromClass(view.classForCoder) == "MPVolumeSlider") {
            let slider = view as! UISlider
            slider.setValue(volume, animated: false)
        }
    }
}

As property volume in MPMusicPlayerController was deprecate in iOS 7. This is only method you can do that.

Nikita Zernov
  • 5,465
  • 6
  • 39
  • 70
8

This (Swift) solution worked great for me: http://weimenglee.blogspot.com/2015/07/ios-tip-programmatically-adjust-device.html

import MediaPlayer

let volumeView = MPVolumeView()
if let view = volumeView.subviews.first as? UISlider{
    view.value = 0.1 //---0 t0 1.0---

}
Lars335
  • 1,730
  • 2
  • 22
  • 35
  • Thanks for this - saved me a few hours or more trying to figure out how to cleanly access that slider. I have to say, this is so boneheaded on Apples part - my app is all gestures, and a slider has no place. Hope they don't reject based on this. I'll leave a nice note in the code. – solenoid Mar 13 '16 at 03:37
  • 3
    Just a few notes: changing volume outside the app (like control center) would cause this to not work anymore. It could be where I was using it, but I put it as a global var in appDelegate and it seems to work. I was also getting a start value of 0 in many circumstances. volumeView.setNeedsLayout() at some point before get/set of values fixes that issue. – solenoid Mar 13 '16 at 17:41
6

You have to use applicationMusicPlayer instead of iPodMusicPlayer to set the system volume:

#import <MediaPlayer/MediaPlayer.h>
musicPlayer = [MPMusicPlayerController applicationMusicPlayer];
musicPlayer.volume = 1; // max volume
musicPlayer.volume = 0; // min volume (mute)
musicPlayer.volume = 0.0625; // 1 bar on the overlay volume display
Paul Slocum
  • 1,454
  • 13
  • 18
3

Here is a little wrapper class that can set the system volume and aslo notify you on any changes (pass your own change responder to setTarget:action:).

Edit: if you hide the MPVolumeView control or don't add it as a subview, then the system will show the default volume square in the middle of the screen whenever you change the volume programmatically. If you don't like what the system does, the solution is to set view coordinates outside of the screen. I edited my code below a bit to reflect this.

@import MediaPlayer;


@interface SysVolumeControl : MPVolumeView
@property (nonatomic) float value; // from 0 to 1.0
- (void)setTarget:(id)target action:(SEL)action;
@end


@implementation SysVolumeControl
{
    UISlider* _slider;
}

- (id)init
{
    if (self = [super initWithFrame:CGRectMake(-300, 0, 200, 50)])
    {
        for (UIView* view in self.subviews)
        {
            if ([view isKindOfClass:UISlider.class])
            {
                _slider = (UISlider*)view;
                break;
            }
        }
    }
    return self;
}

- (float)value
    { return _slider.value; }

- (void)setValue:(float)value
    { _slider.value = value; }

- (void)setTarget:(id)target action:(SEL)action
    { [_slider addTarget:target action:action forControlEvents:UIControlEventValueChanged]; }

@end

Then create an instance and use it:

SysVolumeControl* sysVolumeControl = [SysVolumeControl new];
[myView addSubview:sysVolumeControl];
sysVolumeControl.value = 1.0;
[sysVolumeControl setTarget:self action:@selector(mySysVolumeResponder:)];
mojuba
  • 11,842
  • 9
  • 51
  • 72
1

Creating MPVolumeView on the fly to get the slider and set the volume doesn't work in iOS 11.4 anymore. I solved the issue by adding new MPVolumeView to my UIViewController view, otherwise it didn't set the volume. As I added it to the controller I also need to set the volume view position to be outside of the screen.

The code is in Swift 4:

let volumeControl = MPVolumeView(frame: CGRect(x: 0, y: 0, width: 120, height: 120))

override func viewDidLoad() {
   self.view.addSubview(volumeControl);
}

override func viewDidLayoutSubviews() {
   volumeControl.frame = CGRect(x: -120, y: -120, width: 100, height: 100);
}

func setMaxVolume() {
    let lst = volumeControl.subviews.filter{NSStringFromClass($0.classForCoder) == "MPVolumeSlider"}
    let slider = lst.first as? UISlider

    slider?.setValue(1, animated: false)
}
Northern Captain
  • 1,147
  • 3
  • 25
  • 32
1

The app should not change device global settings and you should not do that. User can do that using standard iOS features. Instead, if you playing a sound/video, then use player's volume:

var player: AVPlayer!
...
player.volume = 0.5 // 50% level
Alexander Volkov
  • 7,904
  • 1
  • 47
  • 44
0

Swift 3 - I use this:

func setVolumeTo(volume: Float) {
  (MPVolumeView().subviews.filter{NSStringFromClass($0.classForCoder) == "MPVolumeSlider"}.first as? UISlider)?.setValue(volume, animated: false)
}
budiDino
  • 13,044
  • 8
  • 95
  • 91
0

Call listenVolumeButton in your viewDidLoad and replace the // line with whatever you want to do.

private func listenVolumeButton() {
    let audioSession = AVAudioSession.sharedInstance()
    try? audioSession.setActive(true)
    audioSession.addObserver(self,
                             forKeyPath: "outputVolume",
                             options: NSKeyValueObservingOptions.new,
                             context: nil)

    hideVolumeView()
}

private func hideVolumeView() {
    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?.sendSubviewToBack(volumeView)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
    switch keyPath {
    case "outputVolume":
        guard let systemSlider = MPVolumeView().subviews.first(where: { NSStringFromClass($0.classForCoder) == "MPVolumeSlider" }) as? UISlider else {
            return
        }

        let avoidLimitsValue = max(min(AVAudioSession.sharedInstance().outputVolume, Float(0.9)), Float(0.1))

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.01) {
            systemSlider.setValue(avoidLimitsValue, animated: false)
        }

        // DO YOUR STUFF HERE
    default:
        break
    }
}
Tulleb
  • 8,919
  • 8
  • 27
  • 55