通过CallKit 来模拟来电
- 首先从官网例子demo把音频文件拿来使用当作来电铃声

- 确保导入 CallKit 以及 PushKit框架

- copy官网例子新建 SpeakerboxCall类
class SpeakerboxCall {
let uuid:UUID
let isOutgoing:Bool
var handle:String?
var stateDidChange:(()->())?
var hasStartedConnectingDidChange:(()->())?
var hasConnectedDidChange:(()->())?
var hasEndedDidChange:(()->())?
var connectingDate: Date? {
didSet {
stateDidChange?()
hasStartedConnectingDidChange?()
}
}
var connectDate: Date? {
didSet {
stateDidChange?()
hasConnectedDidChange?()
}
}
var endDate: Date? {
didSet {
stateDidChange?()
hasEndedDidChange?()
}
}
var isOnHold = false {
didSet {
stateDidChange?()
}
}
var isMuted = false {
didSet {
stateDidChange?()
}
}
var hasStartedConnecting: Bool {
get {
return connectingDate != nil
}
set {
connectingDate = newValue ? Date() : nil
}
}
var hasConnected: Bool {
get {
return connectDate != nil
}
set {
connectDate = newValue ? Date() : nil
}
}
var hasEnded: Bool {
get {
return endDate != nil
}
set {
endDate = newValue ? Date() : nil
}
}
var duration: TimeInterval {
guard let connectDate = connectDate else {
return 0
}
return Date().timeIntervalSince(connectDate)
}
init(uuid: UUID, isOutgoing: Bool = false) {
self.uuid = uuid
self.isOutgoing = isOutgoing
}
func startSpeakerboxCall(completion: ((_ success: Bool) -> Void)?) {
completion?(true)
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 3) {
self.hasStartedConnecting = true
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 1.5) {
self.hasConnected = true
}
}
}
func answerSpeakerboxCall() {
hasConnected = true
}
func endSpeakerboxCall() {
hasEnded = true
}
}
import CallKit
class SpeakerboxCallManager: NSObject {
let callController = CXCallController()
func startCall(handle: String, video: Bool = false) {
let handle = CXHandle(type: .phoneNumber, value: handle)
let startCallAction = CXStartCallAction(call: UUID(), handle: handle)
startCallAction.isVideo = video
let transaction = CXTransaction()
transaction.addAction(startCallAction)
requestTransaction(transaction)
}
func end(call: SpeakerboxCall) {
let endCallAction = CXEndCallAction(call: call.uuid)
let transaction = CXTransaction()
transaction.addAction(endCallAction)
requestTransaction(transaction)
}
func setOnHoldStatus(for call: SpeakerboxCall, to onHold: Bool) {
let setHeldCallAction = CXSetHeldCallAction(call: call.uuid, onHold: onHold)
let transaction = CXTransaction()
transaction.addAction(setHeldCallAction)
requestTransaction(transaction)
}
private func requestTransaction(_ transaction: CXTransaction) {
callController.request(transaction) { error in
if let error = error {
print("Error requesting transaction:", error.localizedDescription)
} else {
print("Requested transaction successfully")
}
}
}
var calls = [SpeakerboxCall]()
func callWithUUID(uuid: UUID) -> SpeakerboxCall? {
guard let index = calls.firstIndex(where: { $0.uuid == uuid }) else { return nil }
return calls[index]
}
func addCall(_ call: SpeakerboxCall) {
calls.append(call)
}
func removeCall(_ call: SpeakerboxCall) {
guard let index = calls.firstIndex(where: { $0 === call }) else { return }
calls.remove(at: index)
}
func removeAllCalls() {
calls.removeAll()
}
}
import CallKit
import AVKit
class CallProviderDelegate: NSObject {
let callManager: SpeakerboxCallManager
private let provider: CXProvider
init(callManager: SpeakerboxCallManager) {
var providerConfiguration:CXProviderConfiguration
if #available(iOS 14.0, *) {
providerConfiguration = CXProviderConfiguration()
} else {
providerConfiguration = CXProviderConfiguration(localizedName: "CallPushPorject")
}
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.supportsVideo = false
providerConfiguration.supportedHandleTypes = [.phoneNumber]
providerConfiguration.ringtoneSound = "Ringtone.aif"
self.callManager = callManager
provider = CXProvider(configuration: providerConfiguration)
super.init()
provider.setDelegate(self, queue: nil)
}
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((Error?) -> Void)? = nil) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
update.hasVideo = hasVideo
provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error == nil {
let call = SpeakerboxCall(uuid: uuid)
call.handle = handle
self.callManager.addCall(call)
}
completion?(error)
}
}
}
extension CallProviderDelegate: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
print("Provider did reset")
for call in callManager.calls {
call.endSpeakerboxCall()
}
callManager.removeAllCalls()
}
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
let call = SpeakerboxCall(uuid: action.callUUID, isOutgoing: true)
call.handle = action.handle.value
call.hasStartedConnectingDidChange = { [weak self] in
self?.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate)
}
call.hasConnectedDidChange = {[weak self] in
self?.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectDate)
}
call.startSpeakerboxCall { success in
if success {
action.fulfill()
self.callManager.addCall(call)
} else {
action.fail()
}
}
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
call.answerSpeakerboxCall()
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
call.endSpeakerboxCall()
action.fulfill()
callManager.removeCall(call)
}
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
call.isOnHold = action.isOnHold
if call.isOnHold {
} else {
}
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
guard let call = callManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
call.isMuted = action.isMuted
action.fulfill()
}
func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
print("Timed out", #function)
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
print("Received", #function)
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
print("Received", #function)
}
}
let callManager = SpeakerboxCallManager()
var providerDelegate: CallProviderDelegate?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.registerPushNotifications()
self.registerVoipPushNotifications()
providerDelegate = CallProviderDelegate(callManager: callManager)
return true
}
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
debugPrint("didReceiveIncomingPushWith")
guard type == .voIP else {
debugPrint("Callkit& pushRegistry didReceiveIncomingPush But Not VoIP")
return
}
debugPrint("收到VoIP")
let uuid = UUID()
let handle = "影"
displayIncomingCall(uuid: uuid, handle: handle, hasVideo: false)
}
func displayIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((Error?) -> Void)? = nil) {
providerDelegate?.reportIncomingCall(uuid: uuid, handle: handle, hasVideo: hasVideo, completion: completion)
}
@objc func callAction() {
debugPrint("打电话")
let callsController = SpeakerboxCallManager()
callsController.startCall(handle: "雷电将军",video: false)
}