I am writing a music player app using Apple Media Player and have a button to change the volume and hide the system default volume overlay. However, all methods I find are based on UIKit.
Like:
let volumeView = MPVolumeView(frame: .zero)
volumeView.clipsToBounds = true
volumeView.alpha = 0.00001
volumeView.showsVolumeSlider = false
view.addSubview(volumeView)
And I tried:
import Foundation
import SwiftUI
import MediaPlayer
struct VolumeView:UIViewRepresentable{
let volumeView:MPVolumeView
func makeUIView(context: Context) -> MPVolumeView {
volumeView.clipsToBounds = true
volumeView.alpha = 0.00001
volumeView.showsVolumeSlider = false
return volumeView
}
func updateUIView(_ uiView: MPVolumeView, context: Context) {
}
typealias UIViewType = MPVolumeView
}
It receives the MPVolumeView I created in my view model and I place it in a SwiftUI view. The indicator disappears but it can't change the volume.
Then I tried to make a new instance of MPVolumeView in UIViewRepresentable and it also didn't work.
I had the same question and your example helped me to figure it out, you were nearly there apparently.
So here is a complete custom Volume Slider in SwiftUI
struct VolumeSliderView: View {
var primaryColor: Color
@StateObject var volumeViewModel = VolumeViewModel()
var body: some View {
ZStack {
VolumeView()
.frame(width: 0, height: 0)
Slider(value: $volumeViewModel.volume, in: 0...1) {
Text("")
} minimumValueLabel: {
Image(systemName: "speaker.fill")
.font(.callout)
.foregroundColor(primaryColor.opacity(0.6))
} maximumValueLabel: {
Image(systemName: "speaker.wave.3.fill")
.font(.callout)
.foregroundColor(primaryColor.opacity(0.6))
} onEditingChanged: { isEditing in
volumeViewModel.setVolume()
}
.tint(primaryColor.opacity(0.6))
}
}
}
import Foundation
import MediaPlayer
struct VolumeView: UIViewRepresentable{
func makeUIView(context: Context) -> MPVolumeView {
let volumeView = MPVolumeView(frame: CGRect.zero)
volumeView.alpha = 0.001
return volumeView
}
func updateUIView(_ uiView: MPVolumeView, context: Context) {
}
}
It seems that an instance of MPVolumeView needs to be present on the screen at any time, even if it's not visible. Seems a bit hacky but works. If I only hide the view in the setVolume()
function and even call volumeView.showsVolumeSlider = false
there, it won't make a difference.
class VolumeViewModel: ObservableObject {
@Published var volume: Float = 0.5
init() {
setInitialVolume()
}
private func setInitialVolume() {
volume = AVAudioSession().outputVolume
}
func setVolume() {
let volumeView = MPVolumeView()
let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.03) {
slider?.setValue(self.volume, animated: false)
}
}
}
Hope it helps somebody, as I just found non working examples here on SO.