问题
I have to change the volume on iPad and using this code:
[[MPMusicPlayerController applicationMusicPlayer] setVolume:0];
But this changing volume and showing system volume bar on iPad. How to change the sound without showing the volume bar?
I know, setVolume:
is deprecated, and everybody says to use MPVolumeView
. If this is the only way to solve my problem, then how to change the volume using MPVolumeView
? I don't see any method in MPVolumeView
that changes the sound.
Should I use some another class together with MPVolumeView
?
But it's preferable to use MPMusicPlayerController
.
Thank you for advice!
回答1:
MPVolumeView
has a slider, and by changing the value of the slider, you can change the device volume. I wrote an MPVolumeView
extension to easily access the slider:
extension MPVolumeView {
var volumeSlider:UISlider {
self.showsRouteButton = false
self.showsVolumeSlider = false
self.hidden = true
var slider = UISlider()
for subview in self.subviews {
if subview.isKindOfClass(UISlider){
slider = subview as! UISlider
slider.continuous = false
(subview as! UISlider).value = AVAudioSession.sharedInstance().outputVolume
return slider
}
}
return slider
}
}
回答2:
For 2018, working on iOS 11.4
You need to change slider.value
after a small delay.
extension MPVolumeView {
static func setVolume(_ volume: Float) {
let volumeView = MPVolumeView()
let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.01) {
slider?.value = volume
}
}
}
Usage:
MPVolumeView.setVolume(0.5)
Objective-C version
回答3:
extension UIViewController {
func setVolumeStealthily(_ volume: Float) {
guard let view = viewIfLoaded else {
assertionFailure("The view must be loaded to set the volume with no UI")
return
}
let volumeView = MPVolumeView(frame: .zero)
guard let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider else {
assertionFailure("Unable to find the slider")
return
}
volumeView.clipsToBounds = true
view.addSubview(volumeView)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { [weak slider, weak volumeView] in
slider?.setValue(volume, animated: false)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { [weak volumeView] in
volumeView?.removeFromSuperview()
}
}
}
}
Usage:
// set volume to 50%
viewController.setVolume(0.5)
回答4:
I don't think there is any way to change the volume without flashing volume control. You should use MPVolumeView
like this:
MPVolumeView* volumeView = [[MPVolumeView alloc] init];
// Get the Volume Slider
UISlider* volumeViewSlider = nil;
for (UIView *view in [volumeView subviews]){
if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
volumeViewSlider = (UISlider*)view;
break;
}
}
// Fake the volume setting
[volumeViewSlider setValue:1.0f animated:YES];
[volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
回答5:
Here's a solution in Swift. It might be a shady one, so I'll let you know if Apple approved this when I publish. Meantime, this works just fine for me:
Define an MPVolumeView and an optional UISlider in your View Controller
private let volumeView: MPVolumeView = MPVolumeView() private var volumeSlider: UISlider?
In the storyboard, define a view that's hidden from the user (height=0 should do the trick), and set an outlet for it (we'll call it hiddenView here). This step is only good if you want NOT to display the volume HUD when changing the volume (see note below):
@IBOutlet weak var hiddenView: UIView!
In viewDidLoad() or somewhere init-y that runs once, catch the UISlider that actually controls the volume into the optional UISlider from step (1):
override func viewDidLoad() { super.viewDidLoad() ... hiddenView.addSubview(volumeView) for view in volumeView.subviews { if let vs = view as? UISlider { volumeSlider = vs break } } }
When you want to set the volume in your code, just set volumeSlider?.value to be anywhere between 0.0 and 1.0, e.g. for increasing the volume:
func someFunc() { if volumeSlider?.value < 0.99 { volumeSlider?.value += 0.01 } else { volumeSlider?.value = 1.0 } }
Important note: This solution will prevent the iPhone's Volume HUD from appearing - either when you change the volume in your code, or when the user clicks the external volume buttons. If you do want to display the HUD, then skip all the hidden view stuff, and don't add the MPVolumeView as a subview at all. This will cause iOS to display the HUD when the volume changes.
回答6:
@udjat 's answer in Swift 3
extension MPVolumeView {
var volumeSlider: UISlider? {
showsRouteButton = false
showsVolumeSlider = false
isHidden = true
for subview in subviews where subview is UISlider {
let slider = subview as! UISlider
slider.isContinuous = false
slider.value = AVAudioSession.sharedInstance().outputVolume
return slider
}
return nil
}
}
回答7:
Version: Swift 3 & Xcode 8.1
extension MPVolumeView {
var volumeSlider:UISlider { // hacking for changing volume by programing
var slider = UISlider()
for subview in self.subviews {
if subview is UISlider {
slider = subview as! UISlider
slider.isContinuous = false
(subview as! UISlider).value = AVAudioSession.sharedInstance().outputVolume
return slider
}
}
return slider
}
}
回答8:
Swift > 2.2, iOS > 8.0,
I didn't find any solution I was looking but I end up doing this as solution:
let volumeView = MPVolumeView()
override func viewDidLoad() {
...
view.addSubview(volumeView)
volumeView.alpha = 0.00001
}
func changeSpeakerSliderPanelControls(volume: Float) {
for subview in self.volumeView.subviews {
if subview.description.rangeOfString("MPVolumeSlider") != nil {
let slider = subview as! UISlider
slider.value = volume
break
}
}
}
回答9:
You can use default UISlider with this code:
import MediaPlayer
class CusomViewCOntroller: UIViewController
// could be IBOutlet
var customSlider = UISlider()
// in code
var systemSlider = UISlider()
override func viewDidLoad() {
super.viewDidLoad()
let volumeView = MPVolumeView()
if let view = volumeView.subviews.first as? UISlider{
systemSlider = view
}
}
next in code just write
systemSlider.value = customSlide.value
回答10:
Here is my volume control for my audio player app:
import UIKit
import MediaPlayer
class UIVolumeSlider: UISlider {
func activate(){
updatePositionForSystemVolume()
guard let view = superview else { return }
let volumeView = MPVolumeView(frame: .zero)
volumeView.alpha = 0.000001
view.addSubview(volumeView)
try? AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
AVAudioSession.sharedInstance().addObserver(self, forKeyPath: "outputVolume", options: .new, context: nil)
addTarget(self, action: #selector(valueChanged), for: .valueChanged)
}
func deactivate(){
AVAudioSession.sharedInstance().removeObserver(self, forKeyPath: "outputVolume")
removeTarget(self, action: nil, for: .valueChanged)
superview?.subviews.first(where: {$0 is MPVolumeView})?.removeFromSuperview()
}
func updatePositionForSystemVolume(){
try? AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
value = AVAudioSession.sharedInstance().outputVolume
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "outputVolume", let newVal = change?[.newKey] as? Float {
setValue(newVal, animated: true)
}
}
@objc private func valueChanged(){
guard let superview = superview else {return}
guard let volumeView = superview.subviews.first(where: {$0 is MPVolumeView}) as? MPVolumeView else { return }
guard let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider else { return }
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.01) {
slider.value = self.value
}
}
}
How to connect:
- Set UIVolumeSlider class in Identity Inspector on storyboard:
- Connect instance to class:
@IBOutlet private var volumeSlider:UIVolumeSlider!
- In viewDidLoad method:
volumeSlider.updatePositionForSystemVolume()
- Activate and deactivate it
shitin didAppear and willDisappear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
volumeSlider.activate()
}
override func viewWillDisappear(_ animated: Bool) {
volumeSlider.deactivate()
super.viewWillDisappear(animated)
}
回答11:
Swift 4:
var player: AVPlayer!
...
player.volume = 0.5 // 50% level
来源:https://stackoverflow.com/questions/33168497/ios-9-how-to-change-volume-programmatically-without-showing-system-sound-bar-po