iOS10適配之 CallKit

iOS10來了绎签,iOS程序員們又有的忙了。

公司產(chǎn)品的核心功能是VoIP語/視頻通話蒲稳,為了與時俱進(jìn)氮趋,就要適配iOS最新的CallKit。關(guān)于CallKit的介紹我就不詳述了江耀,大家可以去看看iOS開發(fā)文檔剩胁、WWDC或者直接Google。

總的來說祥国,CallKit有三大優(yōu)勢:

1.提供系統(tǒng)通話界面,這一點(diǎn)在鎖屏?xí)r體驗(yàn)最明顯昵观。

2.VoIP通話權(quán)限提升到系統(tǒng)級別晾腔,即不是隨便被系統(tǒng)電話打斷,而是可以選擇拒接啊犬。

3.支持系統(tǒng)通訊記錄沉淀與喚起灼擂。

從這三點(diǎn)“升級”可以看出蘋果是非常看中VoIP的市場觉至,現(xiàn)在我們可以像打系統(tǒng)電話一樣使用VoIP了剔应。

那么,我就開門見山的介紹一些API的使用吧语御。

CXProvider

The CXProvider class provides a programmatic interface to an object that represents a telephony provider. A CXProvider object is responsible for reporting out-of-band notifications that occur to the system.

我們首先要初始化一個單例的provider峻贮。其方法是

- (instancetype)initWithConfiguration:(CXProviderConfiguration *)configuration

這里的CXProviderConfiguration很重要,很多我們顯式看到的信息都是在這里面配置好的应闯。

@interface CXProviderConfiguration : NSObject <NSCopying>

//系統(tǒng)來電頁面顯示的app名稱和系統(tǒng)通訊記錄的信息
@property (nonatomic, readonly, copy) NSString *localizedName; 

//來電鈴聲
@property (nonatomic, strong, nullable) NSString *ringtoneSound;

//鎖屏接聽時纤控,系統(tǒng)界面右下角的app圖標(biāo),要求40 x 40大小
@property (nonatomic, copy, nullable) NSData *iconTemplateImageData; 

//最大通話組
@property (nonatomic) NSUInteger maximumCallGroups; // Default 2

//是否支持視頻
@property (nonatomic) BOOL supportsVideo; // Default NO

//支持的Handle類型
@property (nonatomic, copy) NSSet<NSNumber *> *supportedHandleTypes;

@end

我們初始化provider之后還要設(shè)置它代理碉纺,以便執(zhí)行CXProviderDelegate的方法船万。其方法是:

- (void)setDelegate:(nullable id<CXProviderDelegate>)delegate queue:(nullable dispatch_queue_t)queue;

queue一般直接指定為nil,即在main線程執(zhí)行callback骨田。

完成初始化之后耿导,provider 就可以為我們服務(wù)了,這時候來了一個VoIP電話态贤,那么它應(yīng)該報告系統(tǒng)碎节,好讓系統(tǒng)按照它的配置彈出一個系統(tǒng)來電界面。其方法是:

- (void)reportNewIncomingCallWithUUID:(NSUUID *)UUID update:(CXCallUpdate *)update completion:(void (^)(NSError *_Nullable error))completion;

其中UUID是每次隨機(jī)生成的抵卫,標(biāo)記一次通話;CXCallUpdate有點(diǎn)類似CXConfiguration,也是一些配置信息胎撇。

@interface CXCallUpdate : NSObject <NSCopying>

//通話對方的Handle 信息
@property (nonatomic, copy, nullable) CXHandle *remoteHandle;

//對方的名字介粘,可以設(shè)置為app注冊的昵稱
@property (nonatomic, copy, nullable) NSString *localizedCallerName;

//通話過程中再來電,是否支持保留并接聽
@property (nonatomic) BOOL supportsHolding;

//是否支持鍵盤撥號
@property (nonatomic) BOOL supportsDTMF;

//本次通話是否有視頻
@property (nonatomic) BOOL hasVideo;

@end

這些配置信息會影響鎖屏?xí)r的接聽界面上的按鈕狀態(tài)以及多個通話的選擇界面晚树。如果執(zhí)行成功姻采,completion中的error為nil, 否則,不會彈出系統(tǒng)界面爵憎。

由于非本地人為(文章最后解釋)的因素導(dǎo)致的通話結(jié)束慨亲,需要報告系統(tǒng)通話結(jié)束的時間和原因。其方法是:

- (void)reportCallWithUUID:(NSUUID *)UUID endedAtDate:(nullable NSDate *)dateEnded reason:(CXCallEndedReason)endedReason;

如果dateEnded為nil,則認(rèn)為結(jié)束時間是現(xiàn)在宝鼓。

我們還可以動態(tài)更改provider的配置信息CXCallUpdate,比如作為撥打方刑棵,開始沒有地方配置通話的界面,就可以在通話開始時更新這些配置信息愚铡。 其方法是:

- (void)reportCallWithUUID:(NSUUID *)UUID updated:(CXCallUpdate *)update;

作為撥打方蛉签,我們還可以報告通話的狀態(tài)胡陪,以便讓系統(tǒng)知道我們app的VoIP真正的通話開始時間。

通話連接時:

- (void)reportOutgoingCallWithUUID:(NSUUID *)UUID startedConnectingAtDate:(nullable NSDate *)dateStartedConnecting;

通話連接上:

- (void)reportOutgoingCallWithUUID:(NSUUID *)UUID connectedAtDate:(nullable NSDate *)dateConnected;

CXCallController

The CXCallController class provides the programmatic interface for interacting with and observing calls.

初始化:

- (instancetype)initWithQueue:(dispatch_queue_t)queue

queue也是指定執(zhí)行callback的線程碍舍,默認(rèn)是main線程柠座。

在開始或結(jié)束一次通話時,需要提交action事務(wù)請求,這些事務(wù)會交給上面的provider執(zhí)行片橡。

- (void)requestTransaction:(CXTransaction *)transaction completion:(void (^)(NSError *_Nullable error))completion;

Transaction可以通過三種方法添加Action:

- (instancetype)initWithActions:(NSArray<CXAction *> *)actions
- (instancetype)initWithAction:(CXAction *)action;
- (void)addAction:(CXAction *)action;

CXAction是CXCallAction的基類妈经,常見的CXCallAction有:

CXCallAction Subclass Description
CXAnswerCallAction Answers an incoming call
CXStartCallAction Initiates an outgoing call
CXEndCallAction Ends a call
CXSetHeldCallAction Places a call on hold or removes a call from hold
CXSetGroupCallAction Groups a call with another call or removes a call from a group.
CXSetMutedCallAction Mutes or unmutes a call
CXPlayDTMFCallAction Plays a DTMF (dual tone multi frequency) tone sequence on a call

CXProviderDelegate

The CXProviderDelegate protocol defines methods that are called by a CXProvider object when a provider begins or reset, when a transaction is requested, when an action is performed, and when an audio session changes its activation state.

當(dāng)撥打方成功發(fā)起一個通話后,會觸發(fā)

- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action;

當(dāng)接聽方成功接聽一個電話時捧书,會觸發(fā)

- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action;

當(dāng)接聽方拒接電話或者雙方結(jié)束通話時吹泡,會觸發(fā)

- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action;

當(dāng)點(diǎn)擊系統(tǒng)通話界面的Mute按鈕時,會觸發(fā)

- (void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action;

流程圖

一個簡單經(jīng)典的CallKit 通話流程如下圖:


CallKit經(jīng)典通話流程

蘋果官方現(xiàn)在還沒有給出Callkit的完整文檔鳄厌,所以都是自己摸索荞胡,難免有很多坑。

  • 無聲

剛開始做的時候了嚎,會偶然碰到無聲的情況泪漂,這個時候發(fā)現(xiàn)可以在VoIP通話成功后直接結(jié)束系統(tǒng)的通話界面就有聲音了。然后就這么很傻叉地做了歪泳,而且發(fā)現(xiàn)imo一開始也是這么做的萝勤。不過,這樣肯定會帶來問題呐伞,最簡單的就是系統(tǒng)通話紀(jì)錄的時長顯示不對敌卓,因?yàn)樗前凑誧allkit上報的開始和結(jié)束時間算的,這樣毫無理由地結(jié)束當(dāng)然顯示錯誤伶氢。QQ最先寫了一篇文章趟径,講到無聲的處理方法是

在流程開始前setCategory為PlayAndRecord

突然發(fā)現(xiàn)自己的代碼里也寫了這句話,由于以前的代碼邏輯就會處理這種音頻問題癣防,所以懷疑是沖突了蜗巧,反正現(xiàn)在不是很懂,感覺小復(fù)雜蕾盯,去掉就可以了幕屹。

  • 如何在系統(tǒng)通訊錄中增加選項(xiàng)

既然可以沉淀到系統(tǒng)通話紀(jì)錄中,就應(yīng)該可以在通話紀(jì)錄中直接呼出级遭。那么長按系統(tǒng)通訊錄中的“呼叫”如何顯示我們自己的app名稱呢望拖?就像圖中的Whatsup和SpeakerBox一樣。


通話選項(xiàng)

其實(shí)這依賴于CXProviderConfiguration的一個配置項(xiàng):

configuration.supportedHandleTypes = [NSSet setWithObject:@(CXHandleTypePhoneNumber)];

為了支持安裝app就生效挫鸽,可以在AppDelegate.m的didFinishLaunchingWithOptions方法中去做這個配置说敏。

  • 如何從系統(tǒng)通訊中直接呼出

上面解決了選項(xiàng)問題,那么為什么點(diǎn)擊了app的名字沒有任何反應(yīng)呢掠兄?
這需要在AppDelegate.m的continueUserActivity方法中響應(yīng)像云。

INInteraction *interaction = userActivity.interaction;
INIntent *intent = interaction.intent;
    
if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"])
{
    INPerson *person = [(INStartAudioCallIntent *)intent contacts][0];
    CXHandle *handle = [[CXHandle alloc] initWithType:(CXHandleType)person.personHandle.type value:person.personHandle.value];
        
    [[CallKitManager sharedInstance] startCallAction:handle isVideo:NO];
    return YES;
} else if([userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]) {
    INPerson *person = [(INStartVideoCallIntent *)intent contacts][0];
    CXHandle *handle = [[CXHandle alloc] initWithType:(CXHandleType)person.personHandle.type value:person.personHandle.value];
        
    [[CallKitManager sharedInstance] startCallAction:handle isVideo:YES];
    return YES;
}

另外锌雀,在reportNewIncomingCallWithUUID:update:completion:時要指定remoteHandle為對方的Handle。

  • 何種方式結(jié)束

上面的介紹迅诬,我們知道結(jié)束通話可以有兩種方法:

//1
- (void)reportCallWithUUID:(NSUUID *)UUID endedAtDate:(nullable NSDate *)dateEnded reason:(CXCallEndedReason)endedReason;
//2
requestTransaction:CXEndCallAction

那么它們有什么區(qū)別腋逆,該選擇哪個呢?

這個問題我在stackoverflow上提問了侈贷,答案我覺得很清楚惩歉,在此感謝這位@user102008解惑!

You do requestTransactionwith a CXEndCallAction when the user actively chooses to end the call from your app's UI. You do
reportCallWithUUID:endedAtDate:reason:
when it ended not due to user action (i.e. not due to
provider:performEndCallAction:). If you take a look at the allowed
CXCallEndedReasons (failed, remote ended, unanswered, answered elsewhere, and declined elsewhere), they are all reasons not due to the user's action.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俏蛮,一起剝皮案震驚了整個濱河市撑蚌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌搏屑,老刑警劉巖争涌,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異辣恋,居然都是意外死亡亮垫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門伟骨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饮潦,“玉大人,你說我怎么就攤上這事携狭〖汤” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵逛腿,是天一觀的道長稀并。 經(jīng)常有香客問我,道長单默,這世上最難降的妖魔是什么稻轨? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮雕凹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘政冻。我一直安慰自己枚抵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布明场。 她就那樣靜靜地躺著汽摹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苦锨。 梳的紋絲不亂的頭發(fā)上逼泣,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天趴泌,我揣著相機(jī)與錄音,去河邊找鬼拉庶。 笑死嗜憔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的氏仗。 我是一名探鬼主播吉捶,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼皆尔!你這毒婦竟也來了呐舔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤慷蠕,失蹤者是張志新(化名)和其女友劉穎珊拼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體流炕,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澎现,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了浪感。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昔头。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖影兽,靈堂內(nèi)的尸體忽然破棺而出揭斧,到底是詐尸還是另有隱情,我是刑警寧澤峻堰,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布讹开,位于F島的核電站,受9級特大地震影響捐名,放射性物質(zhì)發(fā)生泄漏旦万。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一镶蹋、第九天 我趴在偏房一處隱蔽的房頂上張望成艘。 院中可真熱鬧,春花似錦贺归、人聲如沸淆两。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秋冰。三九已至,卻和暖如春婶熬,著一層夾襖步出監(jiān)牢的瞬間剑勾,已是汗流浹背埃撵。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虽另,地道東北人暂刘。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像洲赵,于是被迫代替她去往敵國和親鸳惯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理叠萍,服務(wù)發(fā)現(xiàn)芝发,斷路器,智...
    卡卡羅2017閱讀 134,711評論 18 139
  • 國慶節(jié)過完了苛谷,回家好好休息一天辅鲸,今天好好分享一下CallKit開發(fā)。最近發(fā)現(xiàn)好多吃瓜問CallKit的VoIP開發(fā)...
    井悟空閱讀 9,317評論 68 21
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,307評論 25 707
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程腹殿,因...
    小菜c閱讀 6,444評論 0 17
  • 近日部門老大自費(fèi)換電腦独悴,最終在京東買了行貨T470P,電腦锣尉、ssd刻炒、內(nèi)存我倆一塊選的。到貨后自沧,包裝都沒拆坟奥,直接讓我...
    趙東偉同學(xué)閱讀 6,296評論 0 1