[iOS][Swift]CallKit

*** Bài dịch từ https://www.raywenderlich.com/150015/callkit-tutorial-ios ***

Mở đầu

Nếu bạn đã từng làm 1 ứng dụng support việc gọi điện thoại (qua VoIP chẳng hạn) thì bạn sẽ thấy việc thông báo cuộc gọi cho các ứng dụng không chính chủ (của Apple) ở iOS nghèo nàn đến mức nào. Nó chỉ đơn giản là 1 cái notification nhỏ bé và người dùng luôn bỏ qua nó. Tuy đã thấu hiểu nhu cầu đó từ rất lâu nhưng mãi đến tận iOS10, Apple mới chính thức hỗ trợ cho nhu cầu vô cùng chính đáng đó, với việc ra mắt CallKit

CallKit bắt đầu được giới thiệu từ iOS 10.0 trở đi, vậy nên để sử dụng CallKit, bạn cần phải chuẩn bị cho mình 1 device iOS 10.0 trở lên, và phải là thiết bị thật, vì trên Simulator không hỗ trợ việc test CallKit cho bạn

CallKit

CallKit bao gồm 2 thành phần chính mà bạn phải làm việc cùng là CXProviderCXCallController

CXProvider

Ứng dụng của bạn sẽ sử dụng XCProvider để report những out-of-band notification (ví dụ như incoming call) tới hệ thống. Khi các sự kiện này xảy ra, CXProvider sẽ tạo call update để notify tới hệ thống.

Ứng dụng communicate với CXProvider thông qua CXProviderDelegate

CXCallController

Nếu CXProvider được dùng để report tới hệ thống thì CXCallController được sử dụng để báo cho hệ thống về những request do chính người dùng khởi tạo ra, ví dụ như khi start action Call. CXCallController sử dụng các CXTransaction (mỗi CXTransaction chứa 1 hoặc nhiều CXAction) để tạo các request gửi tới hệ thống. Và việc của hệ thống là reponse lại cho CXProvider

Nhận cuộc gọi

Khi bạn nhận được 1 cuộc gọi (trong app của bạn) và bạn muốn hiển thị nó lên màn hình Call mặc định của Apple, CXProvider sẽ giúp bạn việc đó. Đơn giản bạn chỉ cần làm

fileprivate let provider: CXProvider
func reportIncommingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) {
    let update = CXCallUpdate()
    update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
    update.hasVideo = hasVideo
    self.provider.reportNewIncomingCall(with: uuid, update: update) { (error) in
        if error == nil {
            let call = Call(uuid: uuid, handle: handle)
            self.callManager.add(call: call)
        }
        completion?(error as NSError?)
    }
}

trong đó UUID sẽ là UUID của máy, bạn có thể lấy nó qua function UUID(), handle chính là cái mà bạn muốn nó hiển thị ở phần tên người gọi

Nếu bạn chọn handle type = .phoneNumber, và handle của bạn là số điện thoại thì iOS sẽ tự động map số điện thoại đó với danh bạ của bạn và hiển thị tên người gọi

Để thực hiện việc nhận cuộc gọi khi lock screen, đừng quên enable tính năng Background Modes cho Voice over IP

Tuy vậy, mọi việc vẫn chỉ dừng lại ở việc hiển thị, mọi tác động tới màn hình Call đó đều không có giá trị gì cả.

Để giải quyết vấn đề này, những việc bạn cần làm đơn giản chỉ là gọi delegate của CXProvider

self.provider.setDelegate(self, queue: nil)

func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
    guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
        action.fail()
        return
    }
    configureAudioSession()
    call.answer()
    action.fulfill()
}
    
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
    startAudio()
}

End Call

Khi ở màn hình khóa, chúng ta dễ dàng EndCall, nhưng khi đã vào ứng dụng chính, việc đó không còn đơn giản như thế nữa, và việc chúng ta cần làm là thêm 1 vài dòng code vào project của mình. Và để gọi action Call ấy, chúng ta sẽ sử dụng tới CXCallController

private let callController = CXCallController()

func end(call: Call) {
    let endCallAction = CXEndCallAction(call: call.uuid)
    let transaction = CXTransaction(action: endCallAction)
    callController.request(transaction) { error in
        if let error = error {
            print("Error = \(error.localizedDescription)")
        } else {
            print("Success")
        }
    }
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
    guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
        action.fail()
        return
    }
    stopAudio()
    call.end()
    action.fulfill()
    callManager.remove(call: call)
}

Kết

Rõ ràng, việc ra mắt CallKit đã giúp cho Dev nâng tầm UX ứng dụng của mình lên 1 tầng cao mới, nếu 1 lần có cơ hội được trải nghiệm trong 1 dự án có sử dụng VoIP, đừng quên suggest khách hàng về CallKit nhé