Here is my volume control for my audio player app:
import UIKit
import MediaPlayer
class UIVolumeSlider: UISlider {
    private let keyVolume = "outputVolume"
    
    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: keyVolume, options: .new, context: nil)
        
        addTarget(self, action: #selector(valueChanged), for: .valueChanged)
    }
    
    
    func deactivate(){
        AVAudioSession.sharedInstance().removeObserver(self, forKeyPath: keyVolume)
        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 == keyVolume, 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 in didAppear and willDisappear:
 
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        volumeSlider.activate()
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        volumeSlider.deactivate()
        super.viewWillDisappear(animated)
    }