12

I wanted to try to detect incoming phone calls in my app. I created a new Swift project from scratch just to try some code. The only thing I did was importing CoreTelephony in the ViewController that is created with every new project and I also changed the viewDidLoad() to:

    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    let callCenter = CTCallCenter()
    NSLog("start")

    callCenter.callEventHandler = {[weak self] (call: CTCall) -> () in

        self?.label.text = call.callState
        NSLog("Call state")
        NSLog(call.callState)

    }

I also tried without the [weak self] since I am new to swift and not sure of what it entails.

When I run my new little app via XCode on my phone nothing happens when a call is received, disconnected or anything else. No error what so ever. Do I have to do something more in order to use the CoreTelephony framework and the CTCallCenter?

Regards Johan

Johan Wiström
  • 373
  • 1
  • 5
  • 13

2 Answers2

36

callEventHandler has been deprecated starting iOS 10.

iOS 10 now employs a new framework to accomplish what you are trying to do, CallKit. This is Apple's new framework that should handle all phone call interruptions. To detect incoming and outgoing calls you use the CXCallObserver. This class uses a protocol CXCallObserverDelegate to inform a registered delegate of a change in calls. I have found that it works well setting AppDelegate as the delegate.

// AppDelegate
var callObserver: CXCallObserver!

// in applicationDidFinishLaunching...
callObserver = CXCallObserver()
callObserver.setDelegate(self, queue: nil) // nil queue means main thread

extension AppDelegate: CXCallObserverDelegate {
    func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
        if call.hasEnded == true {
            print("Disconnected")
        }
        if call.isOutgoing == true && call.hasConnected == false {
            print("Dialing")
        }
        if call.isOutgoing == false && call.hasConnected == false && call.hasEnded == false {
            print("Incoming")
        }

        if call.hasConnected == true && call.hasEnded == false {
            print("Connected")
        }
    }
}
Brandon A
  • 8,153
  • 3
  • 42
  • 77
  • 2
    This approach works in foreground. Is there any way to make it working in background? I didn't find a solution because there is no proper background mode capabilities and no matching extension. – Oleksandr Jun 19 '18 at 09:24
  • 3
    Just a note: Your app might get rejected by Apple if you want to rollout in China. Quote: "Guideline 5.0 - Legal Recently, the Chinese Ministry of Industry and Information Technology (MIIT) requested that CallKit functionality be deactivated in all apps available on the China App Store." – hhamm Jun 27 '18 at 22:14
  • Oh really? I was not aware of this request by the Chinese government to remove CallKit functionality. It couldn't possibly be because the Chinese gov wants their citizens to receive calls unknowingly so they can be listened to, for their security, of course. – Brandon A Jun 27 '18 at 22:19
  • 1
    This approach works in foreground. Is there any way to make it working in background? Pease need help. – nikhil Jan 03 '19 at 07:03
  • @nikhil tell me when you find anything please – BaHaa Jr. Jan 30 '19 at 12:16
  • 1
    Enable UIBackgroundModes with processing for this to work in background. – Abhinav Singh May 24 '21 at 16:04
  • i noticed that this is causing the 'Dialling' to be printed after the 'Disconnected'.. anyone faced this? – Skywalker Aug 12 '21 at 06:51
5

This is an extension on my comment above.

Try making callCenter a property of your view controller instead of just a variable in viewDidLoad.

When you define a variable in a method, the variable and it's value is only present within that method. When the method is finished running, the valuable and their values are clean up so they don't keep using memory (unless the value is used elsewhere).

In your case, you define callCenter and assign it a new CTCallCenter instance. But at the end of viewDidLoad, the CTCallCenter instance is not used anymore so it is clean up from memory. Since it no longer exists, it can't handle the call events.

By adding callCenter as a property of your view controller, it ties the lifespan of the CTCallCenter instance to the lifespan of your view controller. So the CTCallCenter will only be clean up from memory when the view controller is cleaned up from memory.

For more detail, read Automatic Reference Counting in Swift

Craig Siemens
  • 12,942
  • 1
  • 34
  • 51