iOS 替換系統(tǒng)音量提示視圖

實現(xiàn)過程

實現(xiàn)自定義音量提示視圖嫁乘,需要做到以下幾步:
1.激活A(yù)udioSession配置。
2.隱藏系統(tǒng)的音量提示視圖扭吁。
3.監(jiān)聽音量按鈕的觸發(fā)事件撞蜂。

一、激活A(yù)udioSession配置

[[AVAudioSession sharedInstance] setActive:YES error:nil];

需要注意的點是:當(dāng)APP切換到前臺時侥袜,還需要進行激活A(yù)udioSession配置蝌诡,否則會出現(xiàn)收不到KVO的通知場景。

- (void)private_addWillEnterForegroundNotification {
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(didReceiveWillEnterForegroundNotification:)
                                                 name:UIApplicationWillEnterForegroundNotification
                                               object:nil];
}

- (void)didReceiveWillEnterForegroundNotification:(NSNotification *)notification {
    [[AVAudioSession sharedInstance] setActive:YES error:nil];
}

二枫吧、隱藏系統(tǒng)的音量提示視圖

創(chuàng)建MPVolumeView并將其添加到當(dāng)前可見的視圖層中浦旱,同時將其 frame 設(shè)置到不可見區(qū)域。

- (void)onHiddenSystemVolumeView {
        MPVolumeView *mpVolumeView = [[MPVolumeView alloc] init];
        if (@available(iOS 12.0, *)) { //解決卡頓僅處理12以上設(shè)備
            id controller = [mpVolumeView valueForKey:@"lightweightRoutingController"];
           //解決MPVolumeView導(dǎo)致的卡頓
            if (controller && [controller isKindOfClass:NSClassFromString(@"MPAVLightweightRoutingController")] && [controller respondsToSelector:@selector(setDelegate:)])
            {
                [controller performSelector:@selector(setDelegate:) withObject:nil];
            }
        }
        [mpVolumeView setFrame:CGRectMake(-240, -100, 200, 40)];
}

三九杂、監(jiān)聽音量按鈕的觸發(fā)事件

監(jiān)聽系統(tǒng)音量按鈕的觸發(fā)事件颁湖,有兩種方式可以做到,各有優(yōu)劣例隆,下面來做一個簡單對比甥捺。
音量按鈕每觸發(fā)一次,變化量都是 6.25%镀层,連續(xù)按16次镰禾,即可調(diào)節(jié)至最大或最小。

3.1.KVO方式

通過KVO監(jiān)聽[AVAudioSession sharedInstance]的outputVolume屬性鹿响,然后來顯示自定義的 UI 控件羡微。這種方式有一個不好的地方就是:在音量調(diào)節(jié)至最大/最小時,這個時候再調(diào)大/調(diào)小音量惶我,由于outputVolume的值不變妈倔,所以不會觸發(fā)KVO,也就無法展示自定義音量視圖绸贡。代碼大概長下面這樣:

- (void)addObserver {
    [[AVAudioSession sharedInstance] addObserver:self
                                      forKeyPath:NSStringFromSelector(@selector(outputVolume))
                                         options:NSKeyValueObservingOptionNew
                                         context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                       context:(void *)context {
    if ([change isKindOfClass:[NSDictionary class]]) {
        NSNumber *volumeNum = change[@"new"];
        if (volumeNum) {
            [self volumeDidChange:[volumeNum floatValue]];
        }
    }
}

- (void)volumeDidChange:(CGFloat)volume {
    // 顯示自定義音量提示
}

- (void)dealloc {
    [[AVAudioSession sharedInstance] removeObserver:self 
                                         forKeyPath:NSStringFromSelector(@selector(outputVolume))];
}
3.2.通知

這種方式通過監(jiān)聽系統(tǒng)私有(未公開的)通知盯蝴,名字是AVSystemController_SystemVolumeDidChangeNotification,這個監(jiān)聽不會受到最大/最小音量時听怕,調(diào)大/調(diào)小音量的影響捧挺,只要音量鍵按下,始終都會觸發(fā)尿瞭。但是這個通知由于是私有的闽烙,可能存在被拒風(fēng)險,而且將來系統(tǒng)版本該通知名字發(fā)生改變,由于是硬編碼而不像其它系統(tǒng)通知使用的是常量黑竞,會導(dǎo)致監(jiān)聽不到的問題捕发。代碼大概長這樣:

static NSNotificationName const kSystemVolumeDidChangeNotification = @"AVSystemController_SystemVolumeDidChangeNotification";

- (void)addObserver {
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(volumeDidChange:)
                                                 name:kSystemVolumeDidChangeNotification
                                               object:nil];
}

- (void)volumeDidChange:(NSNotification *)notification {
    NSString *category = notification.userInfo[@"AVSystemController_AudioCategoryNotificationParameter"];
    NSString *changeReason = notification.userInfo[@"AVSystemController_AudioVolumeChangeReasonNotificationParameter"];
    if (![category isEqualToString:@"Audio/Video"] || ![changeReason isEqualToString:@"ExplicitVolumeChange"]) {
        return;
    }

    CGFloat volume = [[notification userInfo][@"AVSystemController_AudioVolumeNotificationParameter"] floatValue];
    // 顯示自定義音量提示
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

以上兩種方式各自優(yōu)劣勢都已經(jīng)列出來了,Instagram使用的是通知的方式很魂,嗶哩嗶哩扎酷、騰訊視頻都是用KVO的方式。如果在最大或最小時遏匆,調(diào)節(jié)音量可以接受不展示音量視圖的話法挨,個人推薦使用KVO的形式。

4幅聘、創(chuàng)建自己的音量提示視圖

為了調(diào)用統(tǒng)一且音量視圖層級永遠在最上方(即不被Alert等擋追材伞),自定義的音量視圖使用UIWindow來實現(xiàn)喊暖,然后自定義視圖和系統(tǒng)的視圖加到這個視圖層級上惫企。由于自己創(chuàng)建的UIWindow的hidden屬性默認是YES,所以需要手動將其設(shè)成NO陵叽。

直接使用UIWindow這種方式不太行,會導(dǎo)致其它地方取keyWindow的時候會取錯丛版,導(dǎo)致其他問題巩掺。最終自定義一個Window繼承自UIWindow,然后復(fù)寫becomeKeyWindow方法页畦,在這個方法里讓自身不成為keyWindow同時將[UIApplication sharedApplication].delegate.window設(shè)置為keyWindow胖替,大致代碼長這樣:

@interface VolumeWindow : UIWindow

@end

@implementation VolumeWindow

- (void)becomeKeyWindow {
    [self resignKeyWindow];
    [[UIApplication sharedApplication].delegate.window makeKeyWindow];
}

@end

5、自定義后的問題

問題一:
拍攝頁拍攝之前系統(tǒng)音量提示是可以被替換的豫缨,但是拍攝一段之后独令,莫名其妙音量按鈕按下后自定義提示不見了,出現(xiàn)了系統(tǒng)的鈴聲提示好芭。一臉懵逼燃箭,后面發(fā)現(xiàn)是由于設(shè)置了UIAVCaptureSession的usesApplicationAudioSession為NO,會導(dǎo)致在拍攝之后會變成鈴聲舍败,這個和是否替換系統(tǒng)音量提示無關(guān)招狸。這個屬性是由于項目中很久之前需要兼容iOS6,然后一直遺留著這個屬性設(shè)置沒有刪除邻薯。由于iOS7之后裙戏,AVCaptureSession和應(yīng)用使用的是同一個AudioSession,支持同時播放和錄制且不會受到影響和打斷厕诡,所以不需要再去設(shè)置這個屬性累榜。

問題二:
做了以上操作,在 iPhoneX 下灵嫌,當(dāng)拉起控制中心壹罚,并上下滑調(diào)整音量后冀偶,再回到應(yīng)用,會發(fā)現(xiàn)自定義音量視圖會出現(xiàn)在狀態(tài)欄下面渔嚷,猜測雖然在應(yīng)用內(nèi)自定義音量視圖window層級高于狀態(tài)欄window層級进鸠,但是由于狀態(tài)欄是全局的,在重新進入到應(yīng)用時會出現(xiàn)狀態(tài)欄層級高于音量視圖形病。所以就索性僅在應(yīng)用為active的情況下才處理KVO客年。

最后一個需要注意的點是在語音電話(或者其它使用系統(tǒng)音量的場景下)時,去自己應(yīng)用內(nèi)調(diào)節(jié)音量是無效漠吻,因為這個時候音量其實代表的是系統(tǒng)在占用量瓜,系統(tǒng)優(yōu)先級高于應(yīng)用,所以在這些場景下途乃,即使在應(yīng)用內(nèi)調(diào)節(jié)音量绍傲,也無法觸發(fā)出自己的音量視圖。

最后推薦一個VolumeBar開源庫:https://github.com/gizmosachin/VolumeBar

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耍共,一起剝皮案震驚了整個濱河市烫饼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌试读,老刑警劉巖杠纵,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異钩骇,居然都是意外死亡比藻,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門倘屹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來银亲,“玉大人,你說我怎么就攤上這事纽匙∥耱穑” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵哄辣,是天一觀的道長请梢。 經(jīng)常有香客問我,道長力穗,這世上最難降的妖魔是什么毅弧? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮当窗,結(jié)果婚禮上够坐,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好元咙,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布梯影。 她就那樣靜靜地躺著,像睡著了一般庶香。 火紅的嫁衣襯著肌膚如雪甲棍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天赶掖,我揣著相機與錄音感猛,去河邊找鬼。 笑死奢赂,一個胖子當(dāng)著我的面吹牛陪白,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播膳灶,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼咱士,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了轧钓?” 一聲冷哼從身側(cè)響起序厉,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎聋迎,沒想到半個月后脂矫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡霉晕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了捞奕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牺堰。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖颅围,靈堂內(nèi)的尸體忽然破棺而出伟葫,到底是詐尸還是另有隱情,我是刑警寧澤院促,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布筏养,位于F島的核電站,受9級特大地震影響常拓,放射性物質(zhì)發(fā)生泄漏渐溶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一弄抬、第九天 我趴在偏房一處隱蔽的房頂上張望茎辐。 院中可真熱鬧,春花似錦、人聲如沸拖陆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽依啰。三九已至乎串,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間速警,已是汗流浹背叹誉。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留坏瞄,地道東北人桂对。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像鸠匀,于是被迫代替她去往敵國和親蕉斜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容