iOS 9:如何在不显示系统条形音箱弹出窗口的情况下以编程方式更改音量?
Posted
技术标签:
【中文标题】iOS 9:如何在不显示系统条形音箱弹出窗口的情况下以编程方式更改音量?【英文标题】:iOS 9: How to change volume programmatically without showing system sound bar popup? 【发布时间】:2015-10-16 10:41:39 【问题描述】:我必须更改 iPad 上的音量并使用此代码:
[[MPMusicPlayerController applicationMusicPlayer] setVolume:0];
但是这个改变音量并在 iPad 上显示系统音量栏。 如何在不显示音量条的情况下更改声音?
我知道,setVolume:
已被弃用,每个人都说要使用 MPVolumeView
。如果这是解决我的问题的唯一方法,那么如何使用MPVolumeView
更改音量?我在MPVolumeView
中没有看到任何改变声音的方法。 我应该和MPVolumeView
一起使用其他类吗?
但最好使用MPMusicPlayerController
。
感谢您的建议!
【问题讨论】:
您不应该以编程方式更改音量,这就是setVolume:
被弃用的全部意义所在。您的应用可能会被拒绝。
SWIFTUI 怎么样?我们该怎么做? ***.com/questions/60681324/swiftui-change-sound-level
【参考方案1】:
2018 年,在 ios 11.4 上工作
您需要稍等片刻后更改slider.value
。
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
用法:
MPVolumeView.setVolume(0.5)
Objective-C version
【讨论】:
在我的情况下,它在 iOS 11.4 中工作,只是设置了更长的截止日期(例如,DispatchTime.now() + 0.5) @AndreaGorrieri 它仍然可以在 0.01 延迟下工作,而不会显示系统条形音箱。似乎您在这里还有另一个问题。无论如何,赞成您对其他人的评论有同样的问题。谢谢! @KerCodex 据我所知,每当MPVolumeView
初始化(从 xib、情节提要或以编程方式)时,都会出现此警告。目前,似乎它仍然可以很好地处理此警告,并且没有人有解决方案来避免在这种情况下发出警告。
@SimonPham 我在 iOS 12.0 上尝试过,它按预期工作。确保您在真实设备上运行,而不是模拟器。如果它仍然不起作用,您可以尝试增加延迟时间,就像上面的 AndreaGorrieri 评论一样。
这段代码有缺陷——每次设置音量时,它都会创建一个全新的MPVolumeView
UI 对象。我认为需要异步等待,因为系统可能需要一段时间来初始化这个新对象。我建议在发布时创建一次MPVolumeView
— 然后随时使用它,无需等待,也不会浪费电池。【参考方案2】:
MPVolumeView
有一个滑块,通过改变滑块的值,可以改变设备音量。我写了一个MPVolumeView
扩展来轻松访问滑块:
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
【讨论】:
这行得通吗?我正在尝试使用它但没有成功!我添加了一个视图并将其子类化为 MPVolumeView,但它似乎不起作用。 为我工作!谢谢! 这非常有用 我在 iOS 11.3 上没有问题。我在 iOS 11.4 上做 @OttoBoy @Karl-JohnChow 我在 iOS 11.4 上添加了工作解决方案 ***.com/questions/33168497/…【参考方案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()
用法:
// set volume to 50%
viewController.setVolume(0.5)
【讨论】:
【参考方案4】:这是 Swift 中的一个解决方案。这可能是一个阴暗的,所以我会在我发布时让你知道苹果是否批准了这个。同时,这对我来说很好用:
在你的视图控制器中定义一个 MPVolumeView 和一个可选的 UISlider
private let volumeView: MPVolumeView = MPVolumeView()
private var volumeSlider: UISlider?
在情节提要中,定义一个对用户隐藏的视图(height=0 应该可以解决问题),并为其设置一个出口(我们在这里将其称为 hiddenView)。仅当您不想在更改音量时显示音量 HUD 时,此步骤才有用(请参阅下面的注释):
@IBOutlet weak var hiddenView: UIView!
在 viewDidLoad() 或某个运行一次的 init-y 中,将实际控制音量的 UISlider 捕获到步骤 (1) 中的可选 UISlider 中:
override func viewDidLoad()
super.viewDidLoad()
...
hiddenView.addSubview(volumeView)
for view in volumeView.subviews
if let vs = view as? UISlider
volumeSlider = vs
break
当您想在代码中设置音量时,只需将 volumeSlider?.value 设置为 0.0 到 1.0 之间的任意值,例如增加音量:
func someFunc()
if volumeSlider?.value < 0.99
volumeSlider?.value += 0.01
else
volumeSlider?.value = 1.0
重要提示: 此解决方案将阻止 iPhone 的音量 HUD 出现 - 无论是在您更改代码中的音量时,还是在用户单击外部音量按钮时。如果您确实想显示 HUD,则跳过所有隐藏的视图内容,并且根本不要将 MPVolumeView 添加为子视图。这将导致 iOS 在音量变化时显示 HUD。
【讨论】:
【参考方案5】:我认为没有任何方法可以在不闪烁音量控制的情况下更改音量。你应该像这样使用MPVolumeView
:
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];
【讨论】:
这适用于我在 iOS8 上,但不适用于 iOS9。有 iOS9 的解决方法吗?【参考方案6】:Swift 5 / iOS 13
这是我找到的访问系统音量级别的最可靠方法。这是基于此处的其他答案,但不会浪费能源,也不需要异步等待。
在初始化过程中(如viewDidLoad
),创建一个离屏MPVolumeView
:
var hiddenSystemVolumeSlider: UISlider!
override func viewDidLoad()
let volumeView = MPVolumeView(frame: CGRect(x: -CGFloat.greatestFiniteMagnitude, y:0, width:0, height:0))
view.addSubview(volumeView)
hiddenSystemVolumeSlider = volumeView.subviews.first(where: $0 is UISlider ) as? UISlider
然后在需要时使用隐藏的滑块获取或设置系统音量:
var systemVolume:Float
get
return hiddenSystemVolumeSlider.value
set
hiddenSystemVolumeSlider.value = newValue
【讨论】:
【参考方案7】:@udjat 在 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
【讨论】:
【参考方案8】:版本: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
【讨论】:
你应该如何使用这个? 嗨@JonathanPlackett,你可以这样尝试:self.volumeSlider.value = 1 // get the max volume
BTW,self 表示 MPVolumeView 实例。希望得到帮助。【参考方案9】:
这是我的音频播放器应用的音量控制:
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
如何连接:
-
在 Storyboard 上的 Identity Inspector 中设置 UIVolumeSlider 类:
-
将实例连接到类:
@IBOutlet private var volumeSlider:UIVolumeSlider!
-
在 viewDidLoad 方法中:
volumeSlider.updatePositionForSystemVolume()
-
在didAppear和willDisappear中激活和停用它:
override func viewDidAppear(_ animated: Bool)
super.viewDidAppear(animated)
volumeSlider.activate()
override func viewWillDisappear(_ animated: Bool)
volumeSlider.deactivate()
super.viewWillDisappear(animated)
【讨论】:
使用 viewWillAppear 代替 viewDidAppear。【参考方案10】:Swift > 2.2,iOS > 8.0,
我没有找到我正在寻找的任何解决方案,但我最终将其作为解决方案:
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
【讨论】:
【参考方案11】:您可以通过以下代码使用默认 UISlider:
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
接下来的代码只写
systemSlider.value = customSlide.value
【讨论】:
【参考方案12】:我不想在视图控制器中进行调用,所以我构建了一个不同的解决方案。你可以像这样从任何地方调用它:
SystemVolumeController.shared.systemVolume = 0.5
使用所有 MPVolumeView 解决方案要记住的一点是,当 MPVolumeView 在屏幕上时,当用户按下手机侧面的音量按钮时,屏幕侧面出现的小滑块不会出现。出于这个原因,我在这个解决方案中内置了一个计时器,它会在几秒钟后移除视图。
这里的一些解决方案建议您在设置音量之前需要设置延迟。我没有发现对我来说是这种情况,但您的里程可能会有所不同。
import MediaPlayer
final class SystemVolumeController
static let shared: SystemVolumeController = SystemVolumeController()
private weak var volumeView: MPVolumeView?
private var removeTimer: Timer?
private func getVolumeView() -> MPVolumeView?
if let volumeView = volumeView
return volumeView
else
removeTimer?.invalidate()
let volumeView = MPVolumeView(frame: CGRect(x: -CGFloat.greatestFiniteMagnitude, y:0, width:0, height:0))
guard let window = UIApplication.shared.windows.first else
assertionFailure("No window to add the volume slider to")
return nil
window.addSubview(volumeView)
self.volumeView = volumeView
removeTimer = Timer.scheduledTimer(withTimeInterval: 3,
repeats: false,
block: _ in
volumeView.removeFromSuperview()
)
return volumeView
private var volumeSlider: UISlider?
getVolumeView()?.subviews.first(where: $0 is UISlider ) as? UISlider
var systemVolume: Float
get
AVAudioSession.sharedInstance().outputVolume
set
volumeSlider?.value = newValue
private init()
```
【讨论】:
以上是关于iOS 9:如何在不显示系统条形音箱弹出窗口的情况下以编程方式更改音量?的主要内容,如果未能解决你的问题,请参考以下文章
如何在不点击标记的情况下在 iOS 谷歌地图中显示信息窗口?