Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- Xcode
- Protocol
- swiftUI
- RxCocoa
- 리펙토링
- HIG
- ribs
- 리펙터링
- clean architecture
- ios
- UITextView
- Clean Code
- swift documentation
- 리팩토링
- Refactoring
- 스위프트
- 애니메이션
- uiscrollview
- UICollectionView
- rxswift
- map
- 클린 코드
- Observable
- SWIFT
- tableView
- MVVM
- collectionview
- combine
- uitableview
- Human interface guide
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] 1. 전화걸기, 전화받기, VoIP(Voice over IP) - CallKit 본문
iOS 응용 (swift)
[iOS - swift] 1. 전화걸기, 전화받기, VoIP(Voice over IP) - CallKit
jake-kim 2022. 2. 11. 02:51전화 받기
전화 걸기
1. 전화걸기, 전화받기, VoIP(Voice over IP) - CallKit
2. 전화걸기, 전화받기, VoIP(Voice over IP) - PushKit
iOS에서의 VoIP 개념?
- VoIP(Voice over Internet Protocol): 셀룰러 서비스 대신 인터넷 연결을 사용하여 전화를 걸고 받을 수 있는 프로토콜
- iOS 8이전에는 VoIP앱이 인터넷 연결을 사용하여 전화를 수신하기 위해 서버와 지속적인 연결이 필요했기 때문에 background에서 연결을 유지하면 배터리 소모, 앱이 충돌 등의 다양한 문제가 발생
- iOS8부터 애플에서 PushKit을 만들고 기본 앱 Messenge에 도입하여 최적화 적용 (PushKit 내용은 다음 포스팅 글 참고)
CallKit
- VoIP 서비스를 위해서 시스템에서 제공하는 통화 UI를 제공
- 카톡의 보이스톡, 페이스톡처럼 앱을 이용해 전화가 걸려올 때 일반 전화 수신화면처럼 띄워줄 수 있도록 사용
- 앱간의 calling 서비스 제공이 가능
- CallKit은 CXProvider, CXCallController로 구성
- CXProvider
- CXCallUpdate 인스턴스를 통해 이름과 오디오 전용인지 화상통화인지 속성 정의 가능
- 차례대로 앱에 이벤트를 알리려고 할 때마다 CXAction 인스턴스의 형태로 알림
- CXAction인터페이스이고 구현체안 CXStartCallAction과 CXAnserCallAction 사용
- CXCallController
- Call의 기능 사용 가능 - 전화걸기, 끝내기, mute, 일시정지 등
- CXTransaction(action:) 인스턴스를 사용하여 request
let callController = CXCallController() private func requestTransaction(with action: CXCallAction, completionHandler: (NSError? -> Void)?) { let transaction = CXTransaction(action: action) callController.request(transaction) { error in completionHandler?(error as NSError?) } }
걸려오는 전화 받기 처리
- Call 이벤트가 발생하여 호출이 되면 CXCallUpdate를 생성, CXProvider에 의해 시스템에 전송
- 사용자가 호출에 응답하면 시스템이 CXAnsherCallAction 인스턴스를 CXProvider에 전달
- CXProviderDelegate를 준수하여 처리 가능
걸려오는 전화 받기 구현
- 수신 전화를 받도록 CXProvider객체를 마든 후 전역에 저장
- 앱은 다음에 알아볼 PushKit에서 생성한 VoIP 푸시 알림과 같은 외부 알림에 대한 응답으로 CXProvider에게 호출을 알림
// PushKit에 있는 VoIP 알림 수신 메소드 // MARK: PKPushRegistryDelegate func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, forType type: PKPushType) { // report new incoming call }
- 위 메소드로 들어온 정보를 사용
- uuid와 identifier 사용
- CXProvider의 메소드인 reportNewIncomingCall(with:update:)를 통해 시스템에 calling 요청
if let uuidString = payload.dictionaryPayload["UUID"] as? String, let identifier = payload.dictionaryPayload["identifier"] as? String, let uuid = UUID(uuidString: uuidString) { let update = CXCallUpdate() update.callerIdentifier = identifier provider.reportNewIncomingCall(with: uuid, update: update) { error in // … } }
- reportNewIncomingCall(with:update:)가 호출되면 아래 `CXProviderDelegate`의 메소드가 호출 provider(_:perform:)
- action.fail()이나 action.fultill()을 사용하여 통화 준비가 완료되었다는 메시지 전달 -> 수신 시작
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { action.fulfill() }
- action.fail()이나 action.fultill()을 사용하여 통화 준비가 완료되었다는 메시지 전달 -> 수신 시작
ex) PushKit 없이 전화받기 전체 코드
* CXHandle: phoneNumber나 email과 같이 전화를 받는 사람에게 연락할 수 있는 수단
import UIKit
import CallKit
class ViewController: UIViewController {
private let provider = CXProvider(configuration: CXProviderConfiguration())
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func didTapButton(_ sender: Any) {
provider.setDelegate(self, queue: nil)
// TODO: UUID값과 update값은 PushKit에서 넘어온(pushRegistry 메소드) 정보를 이용하여 사용
// PushKit 포스팅 글 참고: https://ios-development.tistory.com/875
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: "Jake")
provider.reportNewIncomingCall(with: UUID(), update: update) { error in
print(error)
}
}
}
extension ViewController: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
action.fulfill()
}
}
전화 걸기
- 전화 걸기는 3가지가 존재하고, 이 중에서 CallKit은 첫 번째 케이스 사용
- 앱 내에서 통화 요청하여 전화 걸기
- URL 링크 열어서 전화 걸기
- Siri를 사용하여 전화 걸기
- 전화 걸기 구현 방법
- UUID와 CXHandle 인스턴스를 이용하여 수신자 타겟을 정의
* CXHandle: phoneNumber나 email과 같이 전화를 받는 사람에게 연락할 수 있는 수단
let uuid = UUID() let handle = CXHandle(type: .emailAddress, value: "palatable77@gmail.com") let startCallAction = CXStartCallAction(call: uuid, handle: handle)
- 위 startCallAction을 CXTransaction인스턴스를 통해 전화 걸기 요청
// callController는 전역에 존재 private let callController = CXCallController() let transaction = CXTransaction(action: startCallAction) callController.request(transaction) { error in if let error = error { print("Error requesting transaction: \(error)") } else { print("Requested transaction successfully") } }
- 전화 받기와 마찬가지로 델리게이트에서 전화 걸기 시작
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { // configure audio session action.fulfill() }
- 주의사항: 전화걸기에 CXProvider인스턴스는 사용하지 않지만, CXCallController()보다 먼저 전역에서 초기화 되어있지 않으면 오류 발생
Error Domain=com.apple.CallKit.error.requesttransaction Code=2 "(null)"
- UUID와 CXHandle 인스턴스를 이용하여 수신자 타겟을 정의
* 전화 받기 + 전화 걸기 전체 코드
// ViewController.swift
import UIKit
import CallKit
class ViewController: UIViewController {
private let callController = CXCallController()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
}
@IBAction func didTapButton(_ sender: Any) {
self.receiving()
}
@IBAction func didTapOutgoingButton(_ sender: Any) {
self.outgoing()
}
// 전화 받기
private func receiving() {
let provider = CXProvider(configuration: CXProviderConfiguration())
provider.setDelegate(self, queue: nil)
// TODO: UUID값과 update값은 PushKit에서 넘어온(pushRegistry 메소드) 정보를 이용하여 사용
// PushKit 포스팅 글 참고: https://ios-development.tistory.com/875
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: "Jake")
provider.reportNewIncomingCall(with: UUID(), update: update) { error in
print(error ?? "")
}
}
// 전화 하기
private func outgoing() {
let uuid = UUID()
let handle = CXHandle(type: .emailAddress, value: "palatable77@gmail.com")
let startCallAction = CXStartCallAction(call: uuid, handle: handle)
let transaction = CXTransaction(action: startCallAction)
self.callController.request(transaction) { error in
print(error ?? "")
}
}
}
extension ViewController: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
action.fulfill()
}
// 전화 받기 델리게이트 메소드
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
action.fulfill()
}
// 전화 걸기 델리게이트 메소드
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
action.fulfill()
}
}
발신자 식별, 수신 전화 차단
- iOS내에 정보를 저장하는 디렉토리인 Call Directory Extension이라는게 존재
- 해당 디렉토리를 프로젝트에 추가하고 호스트 앱에서 Call Directory Extension을 호출하는 방식으로 동작
- Call Directory Extension 추가
- Xcode -> 현재 Target 선택 -> File -> New -> Target
- Call Directory Extension 선택
- 생성
- 프로젝트에 추가된 것 확인
- Xcode -> 현재 Target 선택 -> File -> New -> Target
발신자 식별 준비
- 전화가 걸려오면 시스템에서 먼저 사용자의 연락처를 참조하여 일치하는 전화번호 탐색
- 일치하는 항목이 없는 경우, 시스템은 앱의 CXCallDirectoryProvider에 정의된 beginRequest(with:)메소드를 참고하여 식별하는 일치 목록 탐색
- -> sns와 같이 시스템 연락처와 별개인 사용자의 연락처 목록을 관리하는 서비스 앱 or 배달 알림과 같이 앱 내에서 시작될 수 있느 수신 전화를 식별하는데 사용
- sns앱에서 Jake와 친구이지만 연락처에 전화번호가 없는 경우, CallDirectory에 명시하여 "(App name) 발신자 ID: Jake Applessed"와 같이 표출
- 위에서 만들어진 `CallDirectoryHandler`안에서 biginRequest()메소드 안에 addIdentificationEntry 메소드 호출
- addIdentificationEntry에 정보가 담기면 전화받을 때 지정된 label로 표출
- 주의 사항: 전화번호에 국가 코드를 반드시 붙여야하고, 숫자 타입의 오름차순으로 배열에 들어가지 않으면 오류 발생
class CustomCallDirectoryProvider: CXCallDirectoryProvider { override func beginRequest(with context: CXCallDirectoryExtensionContext) { let labelsKeyedByPhoneNumber: [CXCallDirectoryPhoneNumber: String] = [821011112222: "발신자 테스트 Jake"] // 01011112222로 전화 오면 "발신자 테스트 Jake"라고 표출 for (phoneNumber, label) in labelsKeyedByPhoneNumber.sorted(by: <) { context.addIdentificationEntry(withNextSequentialPhoneNumber: phoneNumber, label: label) } context.completeRequest() } }
수신 전화 차단 준비
- 전화를 받으면 시스템은 먼저 사용자의 차단 목록을 참조하여 차단해야 하는지 여부를 결정
- 전화 번호가 없거나 차단 목록에 없는 경우, 앱의 beginRequest(with:) 메소드를 참고하여 수신 차단 결정
- 위에서 만들어진 `CallDirectoryHandler`안에서 biginRequest()메소드 안에 addBlockingEntry 메소드 호출
class CallDirectoryHandler: CXCallDirectoryProvider { override func beginRequest(with context: CXCallDirectoryExtensionContext) { context.delegate = self // 수신 차단 let blockedPhoneNumbers: [CXCallDirectoryPhoneNumber] = [821011113333] for phoneNumber in blockedPhoneNumbers.sorted(by: <) { context.addBlockingEntry(withNextSequentialPhoneNumber: phoneNumber) } context.completeRequest() } ... }
ex) 발신자 식별, 수신 차단 코드
class CallDirectoryHandler: CXCallDirectoryProvider {
override func beginRequest(with context: CXCallDirectoryExtensionContext) {
context.delegate = self
let labelsKeyedByPhoneNumber: [CXCallDirectoryPhoneNumber: String] = [821011112222: "발신자 테스트 Jake"] // 01011112222로 전화 오면 "발신자 테스트 Jake"라고 표출
// 발신자 식별
for (phoneNumber, label) in labelsKeyedByPhoneNumber.sorted(by: <) {
context.addIdentificationEntry(withNextSequentialPhoneNumber: phoneNumber, label: label)
}
// 수신 차단
let blockedPhoneNumbers: [CXCallDirectoryPhoneNumber] = [821011113333]
for phoneNumber in blockedPhoneNumbers.sorted(by: <) {
context.addBlockingEntry(withNextSequentialPhoneNumber: phoneNumber)
}
context.completeRequest()
}
...
}
발신자 식별, 수진 전화 차단 활성화
- Call Directory Extension의 번들 ID 확인: "com.ExVoIP.CallDirectory"
- CXCallDirectoryManager.sharedInstance.reloadExtension을 통해 활성화
- withIdentifier에 위에서 확인한 Call Directory의 Bundle ID 입력
import UIKit import CallKit class ViewController: UIViewController { private let callController = CXCallController() override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = .white // 발신자 표시, 수신 차단 기능 활성화 CXCallDirectoryManager.sharedInstance .reloadExtension(withIdentifier: "com.ExVoIP.CallDirectory") { error in print(error ?? "") } } ... }
- withIdentifier에 위에서 확인한 Call Directory의 Bundle ID 입력
* 전체 코드: https://github.com/JK0369/ExCallKit
* 참고
https://medium.com/mindful-engineering/voice-over-internet-protocol-voip-801ee15c3722
https://developer.apple.com/documentation/callkit/cxhandle
https://developer.apple.com/documentation/callkit
https://www.raywenderlich.com/1276414-callkit-tutorial-for-ios
'iOS 응용 (swift)' 카테고리의 다른 글
Comments