注意:
使用 VoIP 推送功能首先需要 开通 TIMPush 服务。
LiveCommunicationKit 需要在 iOS 17.4 及以上系统中使用。
VoIP Push 无法复用 APNs 普通推送证书,需要单独在苹果开发者网站上 申请 VoIP Push 证书。
集成效果
按照本文档接入 Apple LiveCommunicationKit 后,您的 App 将瞬间拥有微信同款的原生通话能力,在进程关闭或锁屏状态下,也能无差别唤醒系统级全屏接听界面:
接听前效果 | 接听后效果 |
![]() | ![]() |
准备条件
开通服务

厂商配置
步骤1:申请 VoIP Push 证书
在申请 VoIP Push 证书之前,请先登录 苹果开发者中心 网站开启 App 的远程推送功能。当您的 AppID 具备了 Push Notification 能力后,按照如下步骤申请并配置 VoIP Push 证书:
1. 登录 苹果开发者中心 网站,单击 Certificates, IDs & Profiles 选项卡,进入 Certificates, Identifiers & Profiles 页面。

2. 单击 Certificates 右侧的+。

3. 在 Create a New Certificate 选项卡中,选择 VoIP Services Certificate,并单击 Continue。

4. 在 Select an App ID for your VoIP Service Certificate 选项卡中,选择您当前的 App 的 BundleID,并单击 Continue。
5. 紧接着,系统提示我们需要一个 Certificate Signing Request(CSR)。
6. 我们接下来制作 CSR 文件。首先在 Mac 上打开钥匙串访问工具(Keychain Access),在菜单中选择钥匙串访问 > 证书助理 > 从证书颁发机构请求证书 (
Keychain Access - Certificate Assistant - Request a Certificate From a Certificate Authority)。
7. 输入用户电子邮件地址(您的邮箱)、常用名称(您的名称或公司名),选择存储到磁盘,单击继续,系统将生成一个
*.certSigningRequest 文件。返回上述步骤 5 中 Apple Developer 网站刚才的页面,单击 Choose File 上传生成的
*.certSigningRequest文件。
8. 单击 Continue 后生成证书,点击 Download 下载对应的证书到本地。
9. 双击打开刚才下载的
voip_services.cer,系统会将其导入钥匙串中。10. 打开钥匙串应用,在登录 > 我的证书,右键导出刚创建的 VoIP Services 的
P12 文件。说明:
保存
P12文件时,请务必为其设置密码。步骤2:上传证书到 IM 控制台
1. 选择您的 IM 应用,进入应用的接入设置。

2. 在厂商配置中,切换到 iOS, 点击添加证书按钮,然后在悬浮页面中选择证书类型,上传 iOS 证书(p12),设置证书密码,单击确认。

说明:
添加证书时,推送类型默认为 APNs,不影响 VoIP 证书的上传和使用。
VoIP Push 证书本身不区分生产环境和测试环境,生产环境和开发环境使用的是同一份 VoIP Push 证书,请分别上传。
上传证书名最好使用全英文(尤其不能使用括号等特殊字符)。
上传证书需要设置密码,无密码收不到推送。
发布 App Store 的证书需要设置为生产环境,否则无法收到推送。
上传的 p12 证书必须是自己申请的真实有效的证书。
3. 上传完成后,记录不同环境下的证书 ID。

说明:
开发环境和生产环境下的证书 ID 要严格区分,请根据实际环境填写。
功能接入
步骤1:完成工程配置
1. 如下图,请在您工程的 Capability 中添加 Push Notification 能力。


2. 如下图,请您在工程 Capability 的 Background Modes 中,开启 Voice over IP 选项。


步骤2: 添加依赖
1. 添加 Pod 依赖:在您项目的
Podfile 文件中添加 pod 'TUICore' 依赖。pod 'TUICore'
2. 安装组件:在终端进入到
Podfile 文件所在的目录,然后执行以下命令安装组件。pod install --repo-update
步骤3:初始化 PushKit 并设置证书 ID
1. 创建一个类来管理 VoIP 推送,初始化 PushKit 用于接收 VoIP 推送通知。
import PushKitimport ImSDK_Plusimport LiveCommunicationKitclass VoIPPushManager: NSObject {private let pushRegistry: PKPushRegistryprivate var certificateID: Int = 0private var voipToken: Data? // 保存 Token,等待 TUILogin 成功后上报// 存储当前通话的 UUIDprivate var currentCallUUID: UUID?// ConversationManager 用于展示系统来电界面(步骤 7)private lazy var conversationManager: ConversationManager = {let configuration = ConversationManager.Configuration(ringtoneName: "phone_ringing.mp3", // 来电铃声文件名iconTemplateImageData: nil, // 来电图标(可选)supportsVideo: true // 是否支持视频通话)let manager = ConversationManager(configuration: configuration)manager.delegate = selfreturn manager}()override init() {pushRegistry = PKPushRegistry(queue: DispatchQueue.main)super.init()pushRegistry.delegate = selfpushRegistry.desiredPushTypes = [.voIP]// 监听 TUILogin 成功的通知NotificationCenter.default.addObserver(self,selector: #selector(onTUILoginSuccess),name: NSNotification.Name("TUILoginSuccess"),object: nil)}deinit {NotificationCenter.default.removeObserver(self)}// 设置证书 IDfunc setCertificateID(_ certificateID: Int) {self.certificateID = certificateID}// TUILogin 成功后上报 Token@objc private func onTUILoginSuccess() {// 收到 TUILogin 成功通知,开始上报 VoIP TokenuploadVoIPToken()}// 上报 VoIP Token 到 IM 后台private func uploadVoIPToken() {guard let token = voipToken else {// VoIP Token 尚未获取return}guard certificateID != 0 else {// 证书 ID 未设置return}let config = V2TIMVOIPConfig()config.certificateID = certificateIDconfig.token = tokenV2TIMManager.sharedInstance().setVOIP(config: config, succ: {// VoIP Token 上报成功}, fail: { code, desc in// VoIP Token 上报失败})}}
2. 在
AppDelegate 中初始化并设置证书 IDclass AppDelegate: UIResponder, UIApplicationDelegate {private lazy var voipPushManager: VoIPPushManager = {let manager = VoIPPushManager()manager.setCertificateID(1234) // 替换为您在 IM 控制台获取的证书 IDreturn manager}()func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {// 触发 voipPushManager 的初始化_ = voipPushManagerreturn true}}
步骤4:获取并保存 VoIP Token
实现
PKPushRegistryDelegate 代理方法,在获取到 VoIP Token 后先保存,等待 TUILogin 成功后再上报。import PushKitimport ImSDK_Plusextension VoIPPushManager: PKPushRegistryDelegate {// 获取到 VoIP Token 后保存(不立即上报)func pushRegistry(_ registry: PKPushRegistry,didUpdate pushCredentials: PKPushCredentials,for type: PKPushType) {guard type == .voIP else { return }// 保存 tokenvoipToken = pushCredentials.token// 将 token 转换为字符串(用于日志)let tokenString = pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined()// VoIP Token 已获取:// 如果 TUILogin 已经完成,立即上报;否则等待 TUILoginSuccess 通知uploadVoIPToken()}// Token 失效func pushRegistry(_ registry: PKPushRegistry,didInvalidatePushTokenFor type: PKPushType) {// VoIP Token 已失效}}
注意:
VoIP Token 的上报必须在 IM SDK 登录成功后才能进行,因此不能在获取 Token 时立即上报。
步骤5:Token 上报
在用户登录时,需要先登录 IM SDK,再登录 TUICore。TUICore 登录成功后会发送
TUILoginSuccess 通知,VoIPPushManager 监听到该通知后会自动上报 VoIP Token。import TUICoreimport AtomicXCore// 步骤1:登录 IM SDK(AtomicXCore 的 LoginStore)LoginStore.shared.login(sdkAppID: sdkAppID,userID: userID,userSig: userSig) { result inswitch result {case .success:// IM SDK 登录成功// 步骤2:登录 TUICoreTUILogin.login(sdkAppID,userID: userID,userSig: userSig) {// TUICore 登录成功// 发送通知,触发 VoIP Token 上报NotificationCenter.default.post(name: NSNotification.Name("TUILoginSuccess"),object: nil)} fail: { code, message in// TUICore 登录失败}case .failure(let error):// IM SDK 登录失败}}
步骤6:接收 VoIP 推送通知
extension VoIPPushManager: PKPushRegistryDelegate {// 收到 VoIP 推送func pushRegistry(_ registry: PKPushRegistry,didReceiveIncomingPushWith payload: PKPushPayload,for type: PKPushType) {guard type == .voIP else { return }// 必须在 5 秒内向系统报告来电,否则会 crashshowIncomingCall(with: payload) // 步骤 7}}
步骤7:展示来电通知
使用 LiveCommunicationKit 向系统报告来电,系统自动展示原生来电界面
import LiveCommunicationKitextension VoIPPushManager {// 向系统报告来电private func showIncomingCall(with payload: PKPushPayload) {// 解析推送数据let payloadDict = payload.dictionaryPayload// 1. 获取来电者名称(必需字段)guard let callerName = payloadDict["voip_caller_name"] as? String else {// 缺少 voip_caller_name 字段return}// 2. 解析 ext 字段获取通话类型(音频/视频)var isVideoCall = falseif let extString = payloadDict["ext"] as? String,let extData = extString.data(using: .utf8),let extDic = try? JSONSerialization.jsonObject(with: extData) as? [String: Any],let voipExtDic = extDic["voip_ext"] as? [String: String],let mediaType = voipExtDic["voip_media_type"] {isVideoCall = (mediaType == "video")}// 创建来电通知内容let uuid = UUID()currentCallUUID = uuid // 保存 UUID,用于后续操作var update = Conversation.Update(members: [Handle(type: .generic, value: callerName, displayName: callerName)])update.capabilities = isVideoCall ? .video : .playingTones// 向系统报告来电(系统自动展示原生来电界面)Task {do {try await conversationManager.reportNewIncomingConversation(uuid: uuid, update: update)// 报告成功} catch {// 报告失败}}}}// MARK: - ConversationManagerDelegateextension VoIPPushManager: ConversationManagerDelegate {func conversationManager(_ manager: ConversationManager, perform action: ConversationAction) {if action is JoinConversationAction {// 用户点击"接听",调用 CallStore 接听通话CallStore.shared.accept { result inswitch result {case .success:// 接听成功,可以在这里跳转到通话页面case .failure(let error):}}} else if action is EndConversationAction {// 用户点击"挂断",调用 CallStore 挂断通话CallStore.shared.hangup { result inswitch result {case .success:case .failure(let error):}}}// 必须调用 fulfill() 通知系统操作已完成action.fulfill()}}
您可以通过配置
Conversation.Update 的以下参数,自定义来电界面显示的来电者信息、通话类型等内容:参数 | 类型 | 是否必需 | 说明 |
members | [Handle] | 必需 | 来电者信息数组,支持多人通话场景。 每个 Handle 包含: type:通常使用 .generic value:来电者唯一标识(建议使用用户 ID) displayName:显示在来电界面的名称 |
capabilities | Conversation.Capabilities | 必需 | 通话类型: .video:视频通话(显示视频图标) .playingTones:语音通话(显示语音图标) |
说明:
用户点击"接听"或"挂断"后,系统会回调 ConversationManagerDelegate 的方法,您需要在回调中处理接听/挂断的业务逻辑(如跳转到通话页面、调用 CallStore API 等)。
发起 VoIP 通话
发起通话
AtomicXCore 的 CallStore 已经默认使用 VoIP 推送,您不需要手动设置 offlinePushInfo 参数。CallStore 内部会自动配置以下推送信息:推送类型:.voIP
推送标题:当前用户 ID
推送描述:"You have a new call"
iOS 铃声:phone_ringing.mp3
import AtomicXCore// 构造通话参数(不需要设置 offlinePushInfo)var params = CallParams()params.timeout = 30 // 超时时间(秒)params.userData = "自定义数据" // 扩展信息(可选)// 发起通话let userIdList = ["被叫用户的 userID"]let mediaType = CallMediaType.video // .video 视频通话,.audio 语音通话CallStore.shared.calls(participantIds: userIdList,callMediaType: mediaType,params: params) { result inswitch result {case .success:// 通话发起成功// 此时可以跳转到您自己的通话页面case .failure(let error):// 通话失败处理}}
说明:
VoIP 推送降级策略
当使用 VoIP 推送时,后台会智能判断证书类型,保证用户仍然能收到推送通知:
上传的是 VoIP 证书:发送 VoIP Push。
上传的是 APNs 证书:自动降级为 APNs 推送。
接听通话
当被叫用户离线时收到 VoIP 推送并点击接听,系统会唤醒您的 App。此时需要监听通话事件并跳转到您自己实现的通话页面。
import AtomicXCoreimport Combineclass YourViewController: UIViewController {private var cancellables = Set<AnyCancellable>()override func viewDidLoad() {super.viewDidLoad()// 监听来电事件CallStore.shared.callEventPublisher.receive(on: DispatchQueue.main).sink { [weak self] event inguard let self = self else { return }switch event {case .onCallReceived(let callId, let mediaType, let userData):// 收到来电,跳转到您自己的通话页面self.showCallViewController(callId: callId, mediaType: mediaType, userData: userData)case .onCallEnded(let callId, let mediaType, let reason, let userId):// 通话结束,返回或更新 UIself.dismissCallViewController()case .onCallStarted(let callId, let mediaType):// 通话已开始break}}.store(in: &cancellables)}private func showCallViewController(callId: String, mediaType: CallMediaType, userData: String) {// 跳转到您自己实现的通话页面let callVC = YourCallViewController()callVC.callId = callIdcallVC.mediaType = mediaTypecallVC.userData = userDatacallVC.modalPresentationStyle = .fullScreenpresent(callVC, animated: true)}private func dismissCallViewController() {// 关闭通话页面dismiss(animated: true)}}
常见问题
获取不到 VoIP Push,如何处理?
1. 检查登录顺序:必须先登录 IM SDK,再登录 TUICore,否则 VoIP Token 无法上报。参考 步骤 5。
2. 首先检查下 App 的运行环境和证书的环境是否一致,证书 ID 是否匹配,如果不一致,无法收到推送。
3. 请确认当前您登录的账号是否处于离线状态:按 home 键切后台、登录后主动杀进程退出。VoIP Push 目前只支持离线状态下的推送。
4. 检查 完成工程配置 是否正确。
5. 尝试重启测试手机来清除系统缓存和内存(很重要)。
应用在收到 VoIP 推送后 crash 怎么办?
这通常是因为没有在 5 秒内向系统发送展示通知的请求。请确保在
didReceiveIncomingPushWith 回调中立即调用 reportNewIncomingConversation 方法。如何自定义来电通知的内容?
您可以通过
Conversation.Update 来自定义来电通知的内容,包括:来电者信息(
displayName)通话类型(
capabilities:.video 或 .playingTones)其他自定义信息

