A Swift Adventure: Building BasicPhone with TwilioClient – Part 2

August 18, 2015
Written by

swift

In the first post in this series we wrote a simple iOS app using Swift that made an outbound phone call using the Twilio Client SDK for iOS.  To do that we set up the basic server infrastructure we need in order to generate a capability token, created a TwiML application that told Twilio what experience to give the caller and used the Client SDK APIs to create a new Device and Connection.

In the second part we’ll look at using the SDK to receive inbound calls.  I’ll show you how to accept calls while the application is running in the foreground, as well as how to use notifications to alert the user of incoming calls while the application is in the background.

Just want to grab the app code and run with it?  Head on over to Github to get the full source for SwiftPhone. If you want to start from where the first post left off, grab the code from Tag 1.

If you want to build your own Swift version of BasicPhone you’ll need a copy of XCode 6.1 or newer.  For you bleeding edgers out there, sorry this won’t work with XCode 7 beta and Swift 2 just yet.

You’ll also need a Twilio account. Don’t worry, trial accounts are free, so if you don’t already have one, head on over to the Twilio website and sign up.

Calling Jenny

Any voice connection coming into Twilio be it PSTN, SIP or Twilio Client can be bridged to an instance of Twilio Client by using the  TwiML noun.  Here we are telling Twilio to try connecting the inbound voice connection to an instance of Twilio Client named “jenny”:

<Response>
    <Dial>
       <Client>jenny</Client>
    </Dial>
</Response>

You can also use the REST API to have Twilio make an outbound call to an instance of Client.  This is what we’ll do to call our iOS application.  Once the call to instance of Client is answered, we’ll use same Greeting.xml we used in the previous post to have Twilio provide an experience to the person answering the call.

Using your favorite Twilio helper library, start an outbound phone call providing the From, To and URL parameters.  In my case I’ll use the Python library:

from twilio.rest import TwilioRestClient
client = TwilioRestClient()
client.calls.create(from_='client:bob', to='client:jenny', url='http://demo.devinrader.info/greeting.xml')

Notice that we can call a client name using the same Twilio API we use to call traditional telephone numbers.  Client names are prepended with the ‘client’ prefix in order to tell Twilio this value is the name of an instance of Client.

Additionally, remember that TwiML app we created in the previous post to handle outbound calls from Twilio Client?  We want to provide the same in-call experience to the answerer of our inbound call so we will pass in that TwiML Apps Sid instead of a URL:

client.calls.create(from_='client:bob', to='client:jenny', url='', application_sid='APXXXXXXXXXXX')

Now that we know how to make calls to our client using the Twilio REST API, let’s see how we receive them in our iOS app.

Your Call Is Important To Us

In the previous post we created an instance of the TCDevice object and saw how to make an outbound phone call using it.  TCDevice however is not only for making outbound calls. Passing it a capability token containing permissions that allow accepting inbound calls tells it to begin to listen for incoming calls.  

If you’ve been following along since Part 1 of this series and built the node application to generate the capability token you should be good to go.  If you went with the option of deploying the Mobile Quickstart to Heroku, you’ll need to make sure are passing the ‘client’ parameter in the URL in order for the quickstart to set the incoming calls permission.

To notify you that it is (or isn’t) listening for calls or has received a call, the TCDevice instance lets you specify a delegate, notifying that delegate of changes in its listening state or of incoming calls via methods defined in the TCDeviceDelegate protocol.

In the previous post we already generated a capability token that includes permissions for accepting inbound calls, so now all we have to do is implement the TCDeviceDelegate protocol on our Phone class and tell our instance of TCDevice where the delegate is.

Because TCDeviceDelegate is an Objective-C protocol, we first need to change the existing Phone class to inherit from NSObject and then specify we want to implement the TCDeviceDelegate protocol

public class Phone : NSObject, TCDeviceDelegate {

The protocol defines four methods, one required and three optional, that we can implement.  For our application we’ll implement the required didStopListeningForIncomingConnections method and two optional methods: didStartListeningForIncomingConnections and didReceiveIncomingConnection in our existing Phone class:

public func deviceDidStartListeningForIncomingConnections(device: TCDevice)->() {
    println("Started listening for incoming connections")
}
    
public func device(device:TCDevice, didStopListeningForIncomingConnections error:NSError)->(){
    println("Stopped listening for incoming connections")
}
    
public func device(device:TCDevice, didReceiveIncomingConnection connection:TCConnection)->() {
    println("Receiving an incoming connection")
}

With the protocol implemented we need to tell the existing TCDevice that its protocol delegate methods are located in this class.  We can do that by updating the initialization of the TCDevice in the Phone class, changing the delegate parameter value from nil to self:

func connectWithParams(params dictParams:Dictionary<String,String> = Dictionary<String,String>()) {
    
    if !self.capabilityTokenValid()
    {
        self.login();
    }
    
    self.connection = self.device?.connect(dictParams, delegate: self);
}

We’re now ready to accept an incoming call.  For now, we’ll just accept every incoming connection but don’t worry, later in the post I’ll show you how to be a bit more discriminating with your incoming calls.

public func device(device:TCDevice!, didReceiveIncomingConnection connection:TCConnection!) {
    println("Receiving an incoming connection")
    self.connection = connection
    self.connection?.accept()
}

Awesome!  Let’s test it out.

Start your app in the iPhone simulator and from the Python console (or wherever you are starting calls from), start a new call to client:jenny.  Your app should receive and accept the incoming call and you should hear the greeting from Twilio.

Who Goes There?

OK. So now we can accept incoming calls, but blindly accepting all calls probably isn’t the greatest idea.  A better experience would be to let the app’s user decide how to handle an incoming call.  Twilio Client allows us to accept, ignore or reject incoming calls. Let’s modify our app to allow the user to choose how to handle the incoming call.

We’ll start with a few changes to our existing Phone class.  When a call is received by our application, we need to give the user time to decide what to do with it.  To hold onto that connection while they decide we’ll create a new variable named pendingConnection:

var device:TCDevice!
var connection:TCConnection!
var pendingConnection:TCConnection!

Now in the didReceiveIncomingConnection function, instead of blindly accepting the incoming connection, we’ll put it into the pendingConnection variable and then send a Notification to allow interested parties to know that there is a new incoming connection:

public func device(device:TCDevice!, didReceiveIncomingConnection connection:TCConnection!) {
    println("Receiving an incoming connection")
    self.pendingConnection = connection

    NSNotificationCenter.defaultCenter().postNotificationName(
        "PendingIncomingConnectionReceived", 
        object: nil, 
        userInfo:nil)
}

Next, we’ll add 3 functions to the Phone class that let developers using the class to act on the connection:

func acceptConnection() {
    self.connection = self.pendingConnection
    self.pendingConnection = nil

    self.connection?.accept()
}
    
func rejectConnection() {
    self.pendingConnection?.reject()
    self.pendingConnection = nil
}
    
func ignoreConnection() {
    self.pendingConnection?.ignore()
    self.pendingConnection = nil
}

Notice that if the connection is accepted we take the pendingConnection and assign it to our connection variable.  In each situation we also set the pendingConnection to nil so we can accept another connection if one arrives.

With the Phone class modified to accept, reject or ignore the incoming call, change the app UI to expose those options to the user.  Add Accept, Ignore and Reject buttons to the UI and make Buttons disabled by default:

Next add Actions and Outlets for each button.  In each Action call the corresponding function from the Phone class:

@IBOutlet weak var btnAnswer: UIButton!
@IBOutlet weak var btnReject: UIButton!
@IBOutlet weak var btnIgnore: UIButton!
        
@IBAction func btnAnswer(sender: AnyObject) {
    self.phone.acceptConnection()
}
    
@IBAction func btnReject(sender: AnyObject) {
    self.phone.rejectConnection()
}

@IBAction func btnIgnore(sender: AnyObject) {
    self.phone.ignoreConnection()
}

Finally, create a Notification observer that listens for the incoming connection Notification:

override func viewDidLoad() {
    super.viewDidLoad()
        
    NSNotificationCenter.defaultCenter().addObserver(
        self, 
        selector:Selector("pendingIncomingConnectionReceived:"),
        name:"PendingIncomingConnectionReceived", object:nil)

    self.phone.login()
}

Add the pendingIncomingConnectionReceived function defined as the Notifications Selector to the ViewController and in it enable the buttons to let the user take action on the call:

func pendingIncomingConnectionReceived(notification:NSNotification) {
   self.btnAnswer.enabled = true
   self.btnReject.enabled = true
   self.btnIgnore.enabled = true
}

Fantastic!  Run the app and make another call from the console.  When the incoming call is detected by the app, the buttons should enable and you should be able to answer, ignore or reject the call.

Back from the Background

Now we have an iOS application that can both make and receive phone calls.  But, what happens if the app is in the background when Twilio sends a call to it?  No worries, the Twilio Client SDK takes care of this for you.

To allow the SDK to do this, from the Project navigator select the SwiftPhone project to open the project properties, select the Capabilities tab and make sure you have set the Audio and VoIP background capabilities for your app.

Setting these capabilities allows the SDK continue to listen for incoming connections even when it is in the background.  The delegate functions defined by TCDeviceDelegate will still get called letting us know the application has received an incoming call, but with the application not active we have no UI so we need a different way to notify the user of the call.  For this we can use LocalNotifications.

In the applications AppDelegate, start by enabling LocalNotifications for the app.  Here we are enabling several different types of notifications, including the Alert notification:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.
    if  UIApplication.instancesRespondToSelector(Selector("registerUserNotificationSettings:")) {
            application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound, categories: nil))
    }
        
    return true
}

Run the app again and it will prompt now you to allow notifications.  This prompt happens only once and the selection is saved for the application.

Next we need to modify the pendingIncomingConnectionReceived function.  We’ll check to see if the app is not active, and if it is not, create and show a LocalNotification:

func pendingIncomingConnectionReceived(notification:NSNotification) {
        
    if UIApplication.sharedApplication().applicationState != UIApplicationState.Active {
        var notification:UILocalNotification = UILocalNotification()
        notification.alertBody = "Incoming Call"
            UIApplication.sharedApplication().presentLocalNotificationNow(notification)
    }
        
    self.btnAnswer.enabled = true
    self.btnReject.enabled = true
    self.btnIgnore.enabled = true
}

Great!  Now run the app and put it in the background.  Make a call from the console and you will see a LocalNotification displayed:

Swipe the notification and the app will become active and allow you to answer the call.  Using LocalNotifications means you’ll also get notifications while your phone is locked:

Again, simply swipe the notification to activate the app and choose how to handle the incoming call.

Finally, answer the incoming call and then put the application into the background.  Notice that the active call continues and iOS displays the in-progress VoIP call banner:

To make the application active again, just tap the banner.

Wrapping It up

That’s it!  In short order we’ve built an iOS application using Swift that can both make and receive VoIP phone calls using the Twilio Client for iOS SDK.  We’ve added the ability to notify  users of incoming calls and to be connected to an active call even if the application is running in the background.

There are million and one ways that you could make this application even better.  Creating a much nicer UI probably the most obvious, but it would also be fun to:

  • create a dial pad that allow users to specify a phone number to dial, or DTMF tone to send
  • add a way to mute and switch between audio outputs
  • build a call timer that shows how long a call has been active
  • integrate the iOS Address Book to make outbound calling easier

What other cool ways can you enhance this simple application? I’d love to see what you come up with.  Drop me a line one twitter or via email and let me know what you’re building with Swift or the Twilio Client for iOS SDK.