iOS 7 後 MPMusicPlayerController.volume 棄用,官方推薦使用 MPVolumeView。但 MPVolumeView 自定義能力極弱:只能替換圖片,無法直接響應手勢、自定義滑塊外觀或綁定自定義 UI。
MPVolumeView 並加入視圖層級(可隱藏)MPVolumeSlider (繼承自 UISlider)UISlider 做 UI 展示,雙向綁定 value#import "ViewController.h"
#import <MediaPlayer/MediaPlayer.h>
@interface ViewController ()
@property (nonatomic, strong) UISlider *systemSlider; // 系統隱藏 Slider
@property (nonatomic, strong) UISlider *customSlider; // 自定義顯示 Slider
@property (nonatomic, assign) CGPoint firstPoint;
@property (nonatomic, assign) CGPoint secondPoint;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 1. 創建系統音量視圖(隱藏)
MPVolumeView *volumeView = [[MPVolumeView alloc] init];
[self.view addSubview:volumeView];
[volumeView sizeToFit];
volumeView.hidden = YES; // 關鍵:不顯示但保持功能
// 2. 提取內部 UISlider
for (UIView *view in volumeView.subviews) {
if ([view.superclass isSubclassOfClass:[UISlider class]]) {
self.systemSlider = (UISlider *)view;
self.systemSlider.autoresizesSubviews = NO;
self.systemSlider.autoresizingMask = UIViewAutoresizingNone;
break;
}
}
// 3. 創建自定義 Slider(可完全自定義外觀)
self.customSlider = [[UISlider alloc] initWithFrame:CGRectMake(20, 100, 280, 30)];
self.customSlider.minimumValue = self.systemSlider.minimumValue; // 0.0
self.customSlider.maximumValue = self.systemSlider.maximumValue; // 1.0
self.customSlider.value = self.systemSlider.value; // 同步初始值
self.customSlider.continuous = YES;
[self.customSlider addTarget:self
action:@selector(customSliderChanged:)
forControlEvents:UIControlEventValueChanged];
[self.view addSubview:self.customSlider];
// 4. 手勢控制:全屏上下滑動調節音量
// 也可用 UIPanGestureRecognizer 更專業
}
- (void)customSliderChanged:(UISlider *)slider {
// 用戶拖動自定義 Slider → 同步系統 Slider
self.systemSlider.value = slider.value;
// 系統 Slider 的 ValueChanged 會自動觸發系統音量變更
}
// 手勢控制實現(覆寫觸摸事件)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
self.firstPoint = [touch locationInView:self.view];
self.customSlider.value = self.systemSlider.value; // 同步起始值
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
self.secondPoint = [touch locationInView:self.view];
// Y 軸向下為正,向上滑動增大音量
CGFloat deltaY = self.firstPoint.y - self.secondPoint.y;
CGFloat step = deltaY / 500.0; // 靈敏度調節
self.systemSlider.value = MAX(0.0, MIN(1.0, self.systemSlider.value + step));
self.customSlider.value = self.systemSlider.value; // UI 同步
self.firstPoint = self.secondPoint; // 更新基準點
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.firstPoint = self.secondPoint = CGPointZero;
}
@end
必須在 viewDidLoad 中 [volumeView sizeToFit] 後提取,否則子視圖尚未佈局完成,subviews 為空。
UIControlEventValueChanged 監聽(系統音量變化、耳機插拔、控制中心操作均會觸發)systemSlider.value = customSlider.valuestep = deltaY / 500.0 中分母控制靈敏度,建議根據屏幕高度動態計算:step = deltaY / self.view.bounds.size.height。
MAX(0.0, MIN(1.0, value)) 確保值始終在 [0,1] 範圍,避免系統 Slider 拋出異常。
- (void)setupGesture {
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(handlePan:)];
pan.maximumNumberOfTouches = 1;
[self.view addGestureRecognizer:pan];
}
- (void)handlePan:(UIPanGestureRecognizer *)pan {
CGPoint velocity = [pan velocityInView:self.view];
if (fabs(velocity.y) > fabs(velocity.x)) { // 垂直滑動主導
CGFloat step = -velocity.y / 10000.0; // 基於速度調節
self.systemSlider.value = MAX(0, MIN(1, self.systemSlider.value + step));
self.customSlider.value = self.systemSlider.value;
}
}
- (void)observeVolumeChanges {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(volumeChanged:)
name:AVSystemController_SystemVolumeDidChangeNotification
object:nil];
}
- (void)volumeChanged:(NSNotification *)note {
NSNumber *volume = note.userInfo[@"AVSystemController_AudioVolumeNotificationParameter"];
self.customSlider.value = volume.floatValue;
}
| 問題 | 原因 | 解決 |
|---|---|---|
| 系統 Slider frame 為 (0,0,35,34) 固定在左上角 | MPVolumeView 內部佈局固定 | hidden=YES 隱藏,用自定義 Slider 顯示 |
| 無法直接實例化 MPVolumeSlider | 私有類,外部不可構造 | 提取現有實例代理 |
| 模擬器無聲音/音量無效 | 模擬器不支持硬件音量 | 真機測試 |
| Control Center 音量變化 UI 不同步 | 未監聽系統通知 | KVO/Notification 監聽同步 |