iOS CallKit 和 LiveCommunicationKit

1,525 阅读3分钟

developer.apple.com/documentati…

developer.apple.com/documentati…

LiveCommunicationKit

在 iOS 17.4 及更高版本中,Apple 引入了 LiveCommunicationKit 框架,为开发者提供了新的 VoIP 通话交互接口。与之前的 CallKit 相比,LiveCommunicationKit 在锁屏状态下不会全屏弹出,也不会在通讯录中留下通话记录。 

目前,关于 LiveCommunicationKit 的官方文档和使用示例相对有限。然而,微信在其 iOS 版 8.0.55 更新中,采用了 LiveCommunicationKit 接口来优化语音接听体验。 

如果您正在开发 VoIP 功能,并希望使用 LiveCommunicationKit,建议您:

  1. 查阅官方文档:访问 Apple 开发者文档,查看 LiveCommunicationKit 的最新信息和使用指南。 
  2. 参考现有应用的实现:研究像微信这样的应用如何集成 LiveCommunicationKit,以获取实际的应用案例。
  3. 关注开发者社区:参与 iOS 开发者论坛和社区,获取其他开发者的经验分享和最佳实践。

自己的微信在升级最新的 8.0.56 版本后,进入微信 App 依次点击 我-设置-消息通知,如果新增了「语音和视频通话用系统电话接听」或「语音通话用弹窗快捷接听」的开关,就说明有了 LiveCommunicationKit 功能。

image.png

CallKit

在 Objective-C 中使用 CallKit 主要用于集成 VoIP(网络电话)功能,使 VoIP 通话可以与 iOS 系统的原生电话 UI 进行交互。下面是详细的实现步骤:

1. 配置 Info.plist

在 Info.plist 添加后台模式,确保 App 在后台仍能接收来电:

<key>UIBackgroundModes</key>
<array>
    <string>voip</string>
</array>
2. 导入 CallKit 和 PushKit

在 ViewController.h 或 AppDelegate.h 中:

#import <CallKit/CallKit.h>
#import <PushKit/PushKit.h>
3. 创建 CallManager 处理通话逻辑

新建 CallManager.h 和 CallManager.m,用于管理 CallKit 功能。

CallManager.h

#import <Foundation/Foundation.h>
#import <CallKit/CallKit.h>

@interface CallManager : NSObject <CXProviderDelegate>

@property (nonatomic, strong) CXProvider *provider;
@property (nonatomic, strong) CXCallController *callController;

- (void)reportIncomingCallWithUUID:(NSUUID *)uuid callerName:(NSString *)callerName;
- (void)startCallWithHandle:(NSString *)handle;
- (void)endCallWithUUID:(NSUUID *)uuid;

@end

CallManager.m

#import "CallManager.h"

@implementation CallManager

- (instancetype)init {
    self = [super init];
    if (self) {
        CXProviderConfiguration *config = [[CXProviderConfiguration alloc] initWithLocalizedName:@"My VoIP App"];
        config.supportsVideo = YES; // 支持视频通话
        config.maximumCallsPerCallGroup = 1;
        config.supportedHandleTypes = [NSSet setWithObjects:@(CXHandleTypePhoneNumber), @(CXHandleTypeEmailAddress), nil];

        self.provider = [[CXProvider alloc] initWithConfiguration:config];
        [self.provider setDelegate:self queue:nil];

        self.callController = [[CXCallController alloc] init];
    }
    return self;
}

#pragma mark - 处理来电
- (void)reportIncomingCallWithUUID:(NSUUID *)uuid callerName:(NSString *)callerName {
    CXCallUpdate *update = [[CXCallUpdate alloc] init];
    update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:callerName];
    update.hasVideo = NO;

    [self.provider reportNewIncomingCallWithUUID:uuid update:update completion:^(NSError * _Nullable error) {
        if (error) {
            NSLog(@"报告来电失败: %@", error.localizedDescription);
        } else {
            NSLog(@"来电成功报告");
        }
    }];
}

#pragma mark - 处理拨打电话
- (void)startCallWithHandle:(NSString *)handle {
    NSUUID *uuid = [NSUUID UUID];
    CXHandle *callHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
    CXStartCallAction *startCallAction = [[CXStartCallAction alloc] initWithCallUUID:uuid handle:callHandle];

    CXTransaction *transaction = [[CXTransaction alloc] initWithAction:startCallAction];
    [self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) {
        if (error) {
            NSLog(@"发起呼叫失败: %@", error.localizedDescription);
        } else {
            NSLog(@"呼叫已发起");
        }
    }];
}

#pragma mark - 处理挂断电话
- (void)endCallWithUUID:(NSUUID *)uuid {
    CXEndCallAction *endCallAction = [[CXEndCallAction alloc] initWithCallUUID:uuid];
    CXTransaction *transaction = [[CXTransaction alloc] initWithAction:endCallAction];

    [self.callController requestTransaction:transaction completion:^(NSError * _Nullable error) {
        if (error) {
            NSLog(@"结束通话失败: %@", error.localizedDescription);
        } else {
            NSLog(@"通话已结束");
        }
    }];
}

#pragma mark - CXProviderDelegate
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
    NSLog(@"用户接听了电话");
    [action fulfill];
}

- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
    NSLog(@"用户挂断了电话");
    [action fulfill];
}

@end

4. 处理 VoIP 推送

在 AppDelegate.h 中:

#import <UIKit/UIKit.h>
#import <PushKit/PushKit.h>
#import "CallManager.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate, PKPushRegistryDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) PKPushRegistry *pushRegistry;
@property (strong, nonatomic) CallManager *callManager;

@end

在 AppDelegate.m 中:

#import "AppDelegate.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.callManager = [[CallManager alloc] init];

    self.pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
    self.pushRegistry.delegate = self;
    self.pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];

    return YES;
}

#pragma mark - PushKit 处理 VoIP 推送
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
    NSString *token = [credentials.token description];
    NSLog(@"VoIP Token: %@", token);
    // 这里可以将 token 发送到服务器
}

- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type {
    NSLog(@"收到 VoIP 推送: %@", payload.dictionaryPayload);

    NSUUID *uuid = [NSUUID UUID];
    NSString *callerName = @"未知来电";

    [self.callManager reportIncomingCallWithUUID:uuid callerName:callerName];
}

@end