CallKit iOS 教程

原文:CallKit Tutorial for iOS
作者:József Vesza
译者:kmyhy

对 VoIP App 开发者来说,iOS 的支持并不友好。尤其是它的通知发送这一块,太糙了。你的 App 允许在后台,你唯一的选择就是使用常规的通知,这也太容易搞丢了。和内置的、丰富的电话 UI 一比,突然你的 App 是如此的不和谐。

幸好,苹果在 iOS 10 中推出了 CallKit,让这一切发生了改变!

在这个教程中,你将通过编写一个 App 领略到 CallKit 的风采:

  • 通过系统服务监听来点和去电。
  • 用电话通讯录识别或拦截来电。

注意:CallKit 无法在模拟器上运行。为了配合本教程,你必须使用一台装有 iOS 10.2 的 iPhone。

开始

从此处下载本教程的开始项目,然后解压缩。为了在设备上调试项目,你必须对代码进行签名。打开项目文件,在项目导航器中选择 Hotline。

你需要修改 bundle ID。选中项目,在 General 窗口,找到 Identity 一栏。将 bundle ID 修改为其它:

CallKit iOS 教程_第1张图片

然后,找到 Signing 栏。从下拉框中选择你的开发团队(以我为例,是我自己的个人团队)。确保勾选上 Automatically manage signing。这允许 Xcode 自动创建 App 所用的 provisioning profile。

CallKit iOS 教程_第2张图片

运行 App 进行测试。

CallKit iOS 教程_第3张图片

目前 App 还没有什么内容,但你会在开始项目中发现几个源文件。它们大部分用于创建UI,处理用户交互,其中比较值得注意的是这两个类:

  • Call 类代表一个电话通话。这个类暴露了一些属性,用于识别呼叫(比如它的 UUID 或者回调),以及生命周期回调,什么时候用户开始、接听或挂起。
  • CallManager 维护了 App 中的呼出列表,拥有添加和移除方法。在本教程中,你会扩展这个类。

CallKit 是什么?

CallKit 是一个新框架,用于改善 VoIP 的体验,允许 App 和原生的 Phone UI 紧密集成,你的 App 将能够:

  • 调用原生的呼入界面,无论锁屏/不锁屏状态。
  • 从原生电话 App 的通讯录、个人收藏、最近通话中发起通话。

本节中,你将学习 CallKit 的构成。下图显示了几个重要对象:

CallKit iOS 教程_第4张图片

在使用 CallKit 时,有两个主要的类:CXProvider和 CXCallController。分别介绍如下。

CXProvider

你的 App 使用 CXProvider 来将外部通知报告给系统。通常是外部事件,比如来电。

当有事件发生,CXProvider 会创建一个 call update 来通知系统。什么是 call update?call update 用于封装新的或者改变了的和通话有关的信息。它用 CXCallUpdate 类来描述,这个类暴露了这些属性:呼入者姓名、是否是音频通话还是视频通话。

当系统想通知 App 有收到一个事件时,它会以 CXAction 的形式通知。CXAction 是一个抽象类,表示电话的动作。针对不同 action,CallKit 会提供不同的 CXAction 实现。例如,呼出用 CXStartCallAction 来表示,CXAnswerCallAction 则用于接听呼入。Action 通过唯一的 UUID 来识别,它要么是 fail 要么是 fulfill。

App 通过 CXProviderDelegate 和 CXProvider 打交道,这个协议定义了 CXProvider 的生命周期事件方法,以及来电 Action。

CXCallController

App 使用 CXCallController 来让系统知道用户发起的请求,比如“呼叫”动作。CXProvider 和 CXCallController 的最大不同在于:CXProvider 的工作是通知系统,而 CXCallController 则代表用户向用户发起请求。

CXCallController 在发起请求时使用了事务。事务用 CXTransaction 来表示,它会包含一个或多个 CXAction 实例。CXCallCotroller 将事务发送给系统,如果一切正常,系统会响应对应的 action 给 CXProvider。

理论还不少,但怎样使用它们呢?

来电

下图显示了来电的高度抽象的模型:

CallKit iOS 教程_第5张图片

  1. 当来电呼入时,App 会创建一个 CXCallUpdate 然后通过 CXProvider 发送给系统。
  2. 系统会发布一个 incoming call 给它的服务。
  3. 当用户接听起电话时,系统会发送一个 CXAnswerCallAction 给 CXProvider。
  4. App 可以通过实现对应的 CXProviderDelegate 协议方法来回应这个动画。

第一步是创建 CXProvider 的委托。

回到 Xcode,在项目导航器中,选中 App 文件夹,点击菜单 File\New…,然后选择 iOS\Source\Swift File。名字命名为 ProviderDelegate,然后点 Create。

在文件中添加代码:

import AVFoundation
import CallKit

class ProviderDelegate: NSObject {
  // 1.
  fileprivate let callManager: CallManager
  fileprivate let provider: CXProvider

  init(callManager: CallManager) {
    self.callManager = callManager
    // 2.
    provider = CXProvider(configuration: type(of: self).providerConfiguration)

    super.init()
    // 3.
    provider.setDelegate(self, queue: nil)
  }

  // 4.
  static var providerConfiguration: CXProviderConfiguration {
    let providerConfiguration = CXProviderConfiguration(localizedName: "Hotline")

    providerConfiguration.supportsVideo = true
    providerConfiguration.maximumCallsPerCallGroup = 1
    providerConfiguration.supportedHandleTypes = [.phoneNumber]

    return providerConfiguration
  }
}

这段代码解释如下:

  1. ProviderDelegate 需要和 CXProvider 和 CXCallController 打交道,因此保持两个对二者的引用。属性用 fileprivate 修饰,这样你就可以从同一个文件中的扩展中访问它们了。
  2. 用一个 CXProviderConfiguration 初始化 CXProvider,前者在后面会定义成一个静态属性。CXProviderConfiguration 用于定义通话的行为和能力。
  3. 为了能够响应来自于 CXProvider 的事件,你需要设置它的委托。这句代码会导致一个编译错误,因为 ProviderDelegate 还没有实现 CXProviderDelegate 协议。
  4. 在这个 App 中,CXProviderConfiguration 支持视频通话、电话号码处理,并将通话群组的数字限制为 1 个。更多的定制化,请参考 CallKit 文档。

在 providerConfiguration 下面,添加一个工具方法:

func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) {
  // 1.
  let update = CXCallUpdate()
  update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
  update.hasVideo = hasVideo

  // 2.
  provider.reportNewIncomingCall(with: uuid, update: update) { error in
    if error == nil {
      // 3.
      let call = Call(uuid: uuid, handle: handle)
      self.callManager.add(call: call)
    }

    // 4.
    completion?(error as? NSError)
  }
}

这个工具方法允许 App 通过 CXProvider API 来报告一个来电。代码解释如下:

  1. 准备向系统报告一个 call update 事件,它包含了所有的来电相关的元数据。
  2. 调用 CXProvider 的reportIcomingCall(with:update:completion:)方法通知系统有来电。
  3. completion 回调会在系统处理来电时调用。如果没有任何错误,你就创建一个 Call 实例,将它添加到 CallManager 的通话列表。
  4. 调用 completion 块,如果它不为空的话。

这个方法被其它类所调用,为了模拟来电呼入。

接下来是实现协议方法。仍然在 ProviderDelegate.swift 文件中,声明一个新的扩展,实现 CXProviderDelegate:


extension ProviderDelegate: CXProviderDelegate {

  func providerDidReset(_ provider: CXProvider) {
    stopAudio()

    for call in callManager.calls {
      call.end()
    }

    callManager.removeAllCalls()
  }
}

CXProviderDelegate 只实现一个 required 的方法,providerDidReset(_:)。当 CXProvider 被 reset 时,这个方法被调用,这样你的 App 就可以清空所有去电,会到干净的状态。在这个方法中,你会停止所有的呼出音频会话,然后抛弃所有激活的通话。

现在 ProviderDelegate 提供了一个方法去报告来电,让我们来用用它!

在项目导航器中选择 App 文件夹,打开 AppDelegate.swift。在类中添加一个新属性:

lazy var providerDelegate: ProviderDelegate = ProviderDelegate(callManager: self.callManager)

providerDelegate 已经整装待发!在 AppDelegate 中添加如下方法:

func displayIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) {
  providerDelegate.reportIncomingCall(uuid: uuid, handle: handle, hasVideo: hasVideo, completion: completion)
}

这个方法向其它类暴露 providerDelegate 的工具方法。

最后一块拼图是将它和 UI 连接到一起。展开 UI/View Controllers 文件夹,打开 CallsViewController.swift,这是 App 主界面的控制器。找到空的 unwindSegueForNewCall(_:)方法,替换为如下代码:

@IBAction private func unwindForNewCall(_ segue: UIStoryboardSegue) {
  // 1.
  let newCallController = segue.source as! NewCallViewController
  guard let handle = newCallController.handle else { return }
  let videoEnabled = newCallController.videoEnabled

  // 2.
  let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
  DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 1.5) {
    AppDelegate.shared.displayIncomingCall(uuid: UUID(), handle: handle, hasVideo: videoEnabled) { _ in
      UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)
    }
  }
}

这段代码的大意是:

  1. 从 NewCallViewController 中读取这次通话的属性,它是 unwind segue 的起始 view controller。
  2. 用户可以在 action 结束之前挂起 App,这样 App 会使用后台任务。

现在一切就绪,运行 App,进行如下操作:

  1. 点击右上角的 + 按钮;
  2. 输入任意数字,在 segmented 控件中选择 Incoming,然后点 Done。
  3. 锁屏。这步很重要,因为这是唯一能够访问完整的原生呼入界面的方法。

几秒钟后,你会看到原生的呼入通话 UI:

CallKit iOS 教程_第6张图片

但是,一旦你要接听电话,你会看到 UI 会仍然停留在下面的状态:

CallKit iOS 教程_第7张图片

这是因为你还没有实现和接听电话对应的方法。回到 Xcode,打开 ProviderDelegate.swift,在类扩展中添加:

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
  // 1.
  guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
    action.fail()
    return
  }

  // 2.
  configureAudioSession()
  // 3.
  call.answer()
  // 4.
  action.fulfill()
}

// 5.
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
  startAudio()
}

这段代码大意如下:

  1. 从 callManager 中获得一个引用,UUID 指定为要接听的动画的 UUID。
  2. 设置通话要用的 audio session 是 App 的责任。系统会以一个较高的优先级来激活这个 session。
  3. 通过调用 answer,你会表明这个通话现在激活。
  4. 在处理一个 CXAction 时,重要的一点是,要么你拒绝它(fail),要么满足它(fullfill)。如果处理过程中没有发生错误,你可以调用 fullfill() 表示成功。
  5. 当系统激活 CXProvider 的 audio session时,委托会被调用。这给你一个机会开始处理通话的音频。

运行 App,再次开始呼入一个通话。当你接听时,系统会成功地变成去电状态。

CallKit iOS 教程_第8张图片

如果你解锁 iPhone,你会看到 iOS 和 App 都会显示出正确的呼出状态。

CallKit iOS 教程_第9张图片

结束通话

接听通话会带来一个问题:没有办法结束通话。这个 App 将会支持两种结束通话的方式:从原生的通话界面,或者从 App 中进行结束。

下图显示这两种结束通话的情况:

CallKit iOS 教程_第10张图片

注意第一步有所不同:当用户从通话界面结束通话(1a)时,系统会自动发送一个 CXEndCallAction 给 CXProvider。但是,如果你想用 Hotline App 来结束通话(1b),那么应该有你来将 CXAction 封装成 CXTransaction,然后请求系统。当系统处理完请求,它会发送 CXEndCallCation 给 CXProvider。

不管哪种方法,你的 App 必须实现相应的 CXProviderDelegate 方法。打开 ProviderDelegate.swift,在类的扩展中添加下列方法:

func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
  // 1.
  guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
    action.fail()
    return
  }

  // 2.
  stopAudio()
  // 3.
  call.end()
  // 4.
  action.fulfill()
  // 5.
  callManager.remove(call: call)
}

还不是太难!代码解释如下:

  1. 从 callManager 获得一个 call 对象。
  2. 当 call 即将结束时,停止这次通话的音频处理。
  3. 调用 end() 方法修改本次通话的状态,以允许其他类和新的状态交互。
  4. 将 action 标记为 fulfill。
  5. 当你不再需要这个通话时,可以让 callManager 回收它。

这只实现了从原生通话界面结束的情况。为了从 App 结束通话,你必须修改 CallManager。在项目导航器的 Call Management 文件夹下,打开 CallManager.swift。

CallManager 需要和 CXCallController 通信,因此需要一个它的引用。添加属性:

private let callController = CXCallController()

在类中添加下列方法:

Now add the following methods to the class:
func end(call: Call) {
  // 1.
  let endCallAction = CXEndCallAction(call: call.uuid)
  // 2.
  let transaction = CXTransaction(action: endCallAction)

  requestTransaction(transaction)
}

// 3.
private func requestTransaction(_ transaction: CXTransaction) {
  callController.request(transaction) { error in
    if let error = error {
      print("Error requesting transaction: \(error)")
    } else {
      print("Requested transaction successfully")
    }
  }
}

代码解释如下:

  1. 先创建一个 CXEndCallAction。将通话的 UUID 传递给构造函数,以便在后面可以识别通话。
  2. 然后将 action 封装成 CXTransaction,以便发送给系统。
  3. 最后,调用 callController 的 request(_:completion:) 。系统会请求 CXProvider 执行这个 CXTransaction,这会导致你刚刚实现的委托方法被调用。

最后是将代码和 UI 连接起来。打开 CallsViewController.swift,在 tableView(_:cellForRowAt:) 方法下面,添加代码:

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
  let call = callManager.calls[indexPath.row]
  callManager.end(call: call)
}

当用户在 cell 上使用轻扫-删除手势时,App 会请求 CallManager 结束对应的通话。

运行 App,执行下列操作:

  1. 点击 + 按钮。
  2. 输入数字,选择 Incoming,点击 Done。
  3. 几秒钟后,你会接到一个来电。当你接听时,你会看到这个通话会在列表中出现。
  4. 在 cell 上向左轻扫,点 End。

这时,通话结束。无论锁屏还是不锁屏,无论 App 是否在前台,这个 App 都会报告通话。

CallKit iOS 教程_第11张图片

其它提供者动作

如果你看过 CXProviderDelegate 的文档,你会注意到 CXProvider 还会执行许多 CXAction,包括静音、群组或者设置呼叫等待(通话保持)。后面一个听起来不错,我们现在就来实现它。

当用户在 cell 上轻扫-删除时,App 会请求 CallManager 去结束对应的通话。

当用户想设置某个通话为“保持”状态,App 会发送一个 CXSetHeldCallAction 给提供者。你的任务就是实现相关的委托方法。打开 ProviderDelegate.swift,在类扩展中添加如下方法:

func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
  guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
    action.fail()
    return
  }

  // 1.
  call.state = action.isOnHold ? .held : .active

  // 2.
  if call.state == .held {
    stopAudio()
  } else {
    startAudio()
  }

  // 3.
  action.fulfill()
}

代码非常简单:

  1. 获得 CXCall 对象之后,我们要根据 action 的 isOnHold 属性来设置它的 state。
  2. 根据状态的不同,分别进行启动或停止音频会话。
  3. 标记 action 为 fulfill。

因为这个动作是用户发起的,我们还需要修改 CallManager 类。打开 CallManager.swift,在 end(call:) 方法后添加方法:

func setHeld(call: Call, onHold: Bool) {
  let setHeldCallAction = CXSetHeldCallAction(call: call.uuid, onHold: onHold)
  let transaction = CXTransaction()
  transaction.addAction(setHeldCallAction)

  requestTransaction(transaction)
}

这段代码和 end(call:) 非常像。事实上,二者唯一的不同是,后者封装在 transaction 中的是一个 CXSetHeldCallAction 对象。这个 action 包含了通话的 UUID 以及保持状态。

然后将这个方法和 UI 连接起来。打开 CallsViewController.swift,找到 UITableViewDelegate 的扩展处。在这个扩展的 tableView(_:titleForDeleteConfirmationButtonForRowAt:) 方法后面添加下列方法。

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  let call = callManager.calls[indexPath.row]
  call.state = call.state == .held ? .active : .held
  callManager?.setHeld(call: call, onHold: call.state == .held)

  tableView.reloadData()
}

当用户在某行上点击,上述代码会改变对应通话的保持状态。

运行 App,开始新的呼入。如果你点击这个通话对应的行,你会注意到状态标签会从 Acitve 变成 On Hold。

CallKit iOS 教程_第12张图片

处理呼出通话

最后还有一个用户发起的动作,需要我们实现,那就是呼出。打开 ProviderDelegate.swift ,在 CXProviderDelegate 类扩展中添加方法:

func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
  let call = Call(uuid: action.callUUID, outgoing: true, handle: action.handle.value)
  // 1.
  configureAudioSession()
  // 2.
  call.connectedStateChanged = { [weak self, weak call] in
    guard let strongSelf = self, let call = call else { return }

    if call.connectedState == .pending {
      strongSelf.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: nil)
    } else if call.connectedState == .complete {
      strongSelf.provider.reportOutgoingCall(with: call.uuid, connectedAt: nil)
    }
  }
  // 3.
  call.start { [weak self, weak call] success in
    guard let strongSelf = self, let call = call else { return }

    if success {
      action.fulfill()
      strongSelf.callManager.add(call: call)
    } else {
      action.fail()
    }
  }
}

当有呼出请求时,provider 会调用这个方法:

  1. 当我们用 UUID 创建出 Call 对象之后,我们就应该去配置 App 的音频会话。和呼入通话一样,你的唯一任务就是配置。真正的处理在后面进行,也就是在 provider(_:didActivate) 委托方法被调用时。
  2. delegate 会监听通话的生命周期。它首先会会报告的就是呼出通话开始连接。当通话最终连上时,delegate 也会被通知。
  3. 调用 call.start() 方法会导致 call 的生命周期变化。如果连接成功,则标记 action 为 fullfill。

现在 ProviderDelegate 已经能够处理呼出了。接下来是让 App 进行一次呼出通话。

打开 CallManager.swift,添加如下方法:

func startCall(handle: String, videoEnabled: Bool) {
  // 1
  let handle = CXHandle(type: .phoneNumber, value: handle)
  // 2
  let startCallAction = CXStartCallAction(call: UUID(), handle: handle)
  // 3
  startCallAction.isVideo = videoEnabled
  let transaction = CXTransaction(action: startCallAction)

  requestTransaction(transaction)
}

这个方法将一个 CXStartCallAction 放到 CXTransaction 中,然后向系统发起请求。

  1. 一个 CXHandle 对象表示了一次操作,同时指定了操作的类型和值。Hotline App 支持对电话号码进行操作,因此我们在操作中指定了电话号码。

  2. 一个 CXStartCallAction 用一个 UUID 和一个操作作为输入。

  3. 你可以通过 action 的 isVideo 属性指定通话是音频还是视频。

然后在 UI 中使用新方法。打开 CallsViewController.swift 将 unwindForNewCall(_:) 方法修改为:

@IBAction private func unwindForNewCall(_ segue: UIStoryboardSegue) {
  let newCallController = segue.source as! NewCallViewController
  guard let handle = newCallController.handle else { return }
  let incoming = newCallController.incoming
  let videoEnabled = newCallController.videoEnabled

  if incoming {
    let backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
    DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 1.5) {
      AppDelegate.shared.displayIncomingCall(uuid: UUID(), handle: handle, hasVideo: videoEnabled) { _ in
        UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier)
      }
    }
  } else {
    callManager.startCall(handle: handle, videoEnabled: videoEnabled)
  }
}

代码中进行了一些调整:当 incoming 为 false 时,view controller 会请求 CallManager 开始一次呼出通话。

这就是打电话功能了。接下来我们测试一下!运行 App。点击 + 按钮,呼出一次通话,确保你选择了 segmented 控件中的 Outgoing。

CallKit iOS 教程_第13张图片

你应该能够在列表中看到新的通话。注意状态标签会根据当前通话的不同阶段变化:

CallKit iOS 教程_第14张图片

管理多个通话

你很容易就会想到,Hotline 的用户会收到多个通话。你可以模拟一下,先呼出一次,再呼入一次,然后在呼入进来之前按下 Home 键。这时,App 会显示如下画面:

CallKit iOS 教程_第15张图片

系统让用户来决定如何处理这种问题。根据用户的选择,它会在一个 CXTransaction 中加入多个 action。例如,如果用户选择结束去电并接听来电,系统会先创建一个 CXEndCallActon,然后是一个CSStartCallAction。两个 action 都放在一个 transaction 中发送给 provider,provider 需要分别进行处理。因此,如果你的 App 能够分别对两个请求进行响应的话,那就不需要再多做什么了!

你可以测试上面说的情况;通话列表会根据你的选择进行显示。App 一次只能处理一个音频会话。如果你选择恢复通话,另一个会自动变成保持通话状态。

CallKit iOS 教程_第16张图片

创建扩展通讯录

通讯录扩展是 CallKit 提供的一个新功能。它允许你的 VoIP App:

  • 将号码添加到系统的黑名单。
  • 识别来电号码或者其它唯一识别标记,比如 email 地址。

当系统收到来电,它会在通讯录中进行陪陪,如果没有找到结果,它会在 App 的扩展通讯录中查找。那就让我们在 Hotline 中添加一个扩展通讯录吧!

返回 Xcode,点击菜单 File\New\Target… 然后选择 Call Directory Extension。Xcode 会自动创建一个新文件 CallDirectoryHandler.swift。在项目导航器中选中它,看一下的内容。

第一个方法是 beginRequest(with:)。这个方法在扩展被初始化时调用。如果发生错误,扩展会告诉宿主 App 取消这次扩展请求(通过调用 cancelRequest(withError:)方法)。另外两个方法用于构建 App 的通讯录。

addBlockingPhoneNumber(to:) 方法用于定义要阻塞的电话号码。修改这个方法为:

private func addBlockingPhoneNumbers(to context: CXCallDirectoryExtensionContext) throws {
  let phoneNumbers: [CXCallDirectoryPhoneNumber] = [ 1234 ]
  for phoneNumber in phoneNumbers {
    context.addBlockingEntry(withNextSequentialPhoneNumber: phoneNumber)
  }
}

以指定的号码调用 addBlockingEntry(withNextSequentialPhoneNumber:) 方法,将这个号码添加到黑名单。当某个号码被阻塞,系统电话 provider 不会显示任何来自这个号码的来电。

然后是 addIdentificationPhoneNumbers(to:) 方法。将这个方法修改为:

private func addIdentificationPhoneNumbers(to context: CXCallDirectoryExtensionContext) throws {
  let phoneNumbers: [CXCallDirectoryPhoneNumber] = [ 1111 ]
  let labels = [ "RW Tutorial Team" ]

  for (phoneNumber, label) in zip(phoneNumbers, labels) {
    context.addIdentificationEntry(withNextSequentialPhoneNumber: phoneNumber, label: label)
  }
}

将某个号码和 label 作为参数调用 addIdentificationEntry(withNextSequentialPhoneNumber:label:) 方法将创建一个新的 identification entry。当系统收到这个号码的来电时,电话 UI 上会显示这个 label 给用户。

来测试一下。在设备上运行 App。但是你的扩展并没有被激活。你需要经过以下步骤来激活它:

  1. 打开设置程序。
  2. 选择电话。
  3. 选择来电阻止与身份识别
  4. 将 Hotline 开关打开

注意:如果你无法让系统识别出你的扩展,请退出 App 并重新打开。有时候 iOS 在使用你的扩展时会有点问题。

IMG_6756.png

测试来电阻止其实很简单:点开 Hotline,以号码 1234 来进行一次呼入。你会注意到系统不会报告任何来电。事实上,你可以在 ProviderDelegate 的reportIncomingCall(uuid:handle:hasVideo:completion:) 方法中打一个断点,你会看到 reportNewIncomingCall 这句代码甚至会报错。

要测试身份识别,再次运行 Hotline,模拟一次呼入,这次,号码输入 1111。你会看到如下的通话界面:

CallKit iOS 教程_第17张图片

恭喜!你创建了一个 App,用 CallKit 提供了原生的 VoIP 体验!:]

结束

你可以从这里下载最终完成的项目。

如果你想学习更多关于 CallKit 的内容,请看 WWDC 2016 第 230 讲会议视频。

希望你喜欢这篇 CallKit 教程。有任何建议或问题,请在下面留言。

你可能感兴趣的:(iPhone开发)