Getting Started with iOS libSoftphone

This section describes the steps to create an iOS application that can place and receive calls using libSoftphone SDK.

Adding SoftphoneSwift Dependency

To add a package dependency to the Xcode project:

  1. Open your Xcode project.

  2. Select the Package Dependencies tab.

  3. Select the + button.

  4. In the Enter Package URL field, add the URL of the libSoftphone Swift package https://github.com/acrobits/SoftphoneSwiftPackage-saas.git.

  5. Select the Add Package button.

After adding the dependency, Xcode automatically includes the Softphone dependencies.

Initializing the SDK

Use the initialize method to initialize SoftphoneBridge. This method takes XML string as a parameter. This string represents an XML structure containing the license key and the configuration of the SDK.

 1let license = """
 2    <root>
 3        <saas>
 4            <identifier>your license key</identifier>
 5        </saas>
 6    </root>
 7    """
 8
 9// This method is used for initializing an instance of SDK
10SoftphoneBridge.initialize(license)

Note

Replace your license key between the <identifier> XML tags with a valid license key obtained in the Acrobits licensing portal. This key is required for initializing the SDK. See step 1 in the Quick Start Guide.

Reporting Application States

libSoftphone SDK must have accurate information about the current state of the application to handle SIP registration, push calls, and overall SDK functionality properly. To report the application state changes to the SDK, call the update method of the SoftphoneBridge.instance().state() instance. This method takes the InstanceStateType enum as a parameter. This enum contains all possible application states.

The following code snippet illustrates the functions that handle different application state transitions using the update method:

 1func applicationDidBecomeActive(_ application: UIApplication) {
 2    let bg = UIApplication.shared.applicationState == .background
 3    SoftphoneBridge.instance().state().update(bg ? InstanceState_Background : InstanceState_Active)
 4}
 5
 6func applicationWillResignActive(_ application: UIApplication) {
 7    SoftphoneBridge.instance().state().update(InstanceState_Inactive)
 8}
 9
10func applicationDidEnterBackground(_ application: UIApplication) {
11    SoftphoneBridge.instance().state().update(InstanceState_Background)
12}
13
14func applicationWillEnterForeground(_ application: UIApplication) {
15    SoftphoneBridge.instance().state().update(InstanceState_Active)
16}

Managing SIP Account

Set up a SIP account before you can place calls. SIP accounts are specified in XML format.

To view all the recognized XML nodes and values that can be used to configure SIP accounts, go to the Account XML documentation at doc.acrobits.net/cloudsoftphone/account.html#account-xml.

Creating a SIP Account

There are two ways to create a SIP account:

  • Include the following code to create an account with a basic configuration containing the username, password, and host:

     1let xml = XmlTree(name: "account")
     2xml?.setAttribute(name: "id", value: "sip")
     3xml?.setNodeValue(value: "Sip Account", name: "title")
     4xml?.setNodeValue(value: "1125", name: "username")
     5xml?.setNodeValue(value: "misscom", name: "password")
     6xml?.setNodeValue(value: "pbx.acrobits.cz", name: "host")
     7xml?.setNodeValue(value: "udp", name: "transport")
     8
     9SoftphoneBridge.instance().registration().saveAccount(xml)
    10SoftphoneBridge.instance().registration().updateAll()
    
  • If you have a XML string for an account, parse the XML string using the XmlTree class, and then pass it to the saveAccount method.

     1let sipAccount = """
     2    <account id=\"sip\">
     3        <title>Sip Account</title>
     4        <username>1125</username>
     5        <password>misscom</password>
     6        <host>pbx.acrobits.cz</host>
     7        <transport>udp</transport>
     8    </account>
     9"""
    10
    11let xml = XmlTree.parse(sipAccount)
    12SoftphoneBridge.instance().registration().saveAccount(xml)
    13SoftphoneBridge.instance().registration().updateAll()
    

Note the following:

  • The id attribute must be specified to identify an account. If not, a unique one is generated for that account upon calling the saveAccount method.

  • Calling SoftphoneBridge.instance().registration().saveAccount() with an XML that has the same id as an existing account replaces the existing account with the new values.

  • If you save a new account with an id that matches the ID of an existing account but different account configuration, the new account (re)registers asynchronously.

  • The account settings are stored on disk, so you do not have to recreate the account when the application restarts.

Deleting an Account

Call the deleteAccount method of the SoftphoneBridge.instance().registration() instance to delete accounts. This method takes account ID as a parameter.

1SoftphoneBridge.instance().registration().deleteAccount("sip")

Observing Events from SDK

You can implement your own Softphone Delegates to receive notifications about important events that occur within the SDK.

  • To set up the delegates for the SDK, create the SoftphoneObserverProxyBridge instance and configure it to act as the delegate for the softphone instance. Set an observer immediately after the SDK initialization.

    The following example code snippet demonstrates the setup:

    1let softphoneObserverProxy = SoftphoneObserverProxyBridge()
    2softphoneObserverProxy.delegate = self;
    3SoftphoneBridge.instance().setObserver(softphoneObserverProxy)
    
  • You can implement the SoftphoneDelegateBridge protocol and all the methods of this protocol in your class. Some of these methods are optional. The following code snippet gives examples of implementing the SoftphoneDelegateBridge protocol:

     1// This delegate will be called when the registration state changes
     2func onRegistrationStateChanged(state: RegistratorStateType, accountId: String!) {
     3
     4}
     5
     6// This delegate will be called when a new event arrives (i.e. incoming call/message)
     7func onNewEvent(_ event: SoftphoneEvent!) {
     8
     9}
    10
    11// This delegate will be called when the state of the call changes
    12func onCallStateChanged(state: CallStateType, call: SoftphoneCallEvent!) {
    13
    14}
    15
    16// This delegate will be called when the network change is detected
    17func onNetworkChangeDetected(_ network: NetworkType) {
    18
    19}
    20
    21// This delegate will be called when the hold state of a call is changed
    22func onHoldStateChanged(states: CallHoldStates!, call: SoftphoneCallEvent!) {
    23
    24}
    
  • Set up Softphone_Cx_Delegate so that you can manage the interaction with iOS CallKit:

    • To modify CXCallUpdate before it is reported to the system.

    • To modify CXProviderConfiguration created by the SDK or to create a new one.

    The following code snippet illustrates setting up the delegate for the Softphone_Cx instance.

    1Softphone_Cx.instance().delegate = self
    

Placing a Call

Use the SDK to enable the application to make calls.

To place calls:

  1. Create SoftphoneCallEvent with a specified accountId and uri.

  2. Associate the SoftphoneCallEvent object with a specific stream.

  3. Send the call event using the post method provided by the SoftphoneBridge.instance().events() instance.

    The method returns the status of the post as the EventsPostResult enum.

The following snippet code illustrates the process of initiating a call:

 1func call(number: String) {
 2    if number.isEmpty {
 3        return;
 4    }
 5
 6    let call = SoftphoneCallEvent.create(withAccountId: "sip", uri: number)
 7    let stream = SoftphoneEventStream.load(SoftphoneStreamQuery.legacyCallHistoryStreamKey())
 8
 9    call?.setStream(stream)
10    call?.transients.set("voiceCall", forKey: "dialAction")
11
12    let result = SoftphoneBridge.instance().events().post(call)
13
14    if result != PostResult_Success {
15        print("Failed to post call event")
16    }
17}

Managing Preferences

The SDK provides the app-specific preferences function to manage the Preferences menu items in the application setting. To do so, use the SoftphoneBridge.instance().settings().getPreferences() instance, which provides access to both read-write and read-only properties related to preferences.

Reading Preferences

You can read various properties and check the state of a preference, such as SIP logging, acoustic echo suppression, and pressing keypad volume.

For example, to check if SIP traffic logging is enabled, use the following codes:

1if SoftphoneBridge.instance().settings().getPreferences().trafficLogging {
2    print("SIP traffic logging is enabled")
3}

Changing Preferences

You can modify various properties and change the value of a preference.

For example, the following code snippet demonstrates that setting the trafficLogging property as true to enable SIP traffic logging:

1SoftphoneBridge.instance().settings().getPreferences().trafficLogging = true

Using Call Event History

Use the Call Event History feature to store call history in the device’s local storage and to display a list of recent calls in your app. This feature is disabled by default.

  1. To enable the Call Event History feature, add the following code in the app’s provisioning XML file:

    1<root>
    2    <saas>
    3        <identifier>your license key</identifier>
    4    </saas>
    5    <prefKeys>
    6        <prop name="historyStorageType" default="sql"/>
    7    </prefKeys>
    8</root>
    

    You can refer to the Initializing the SDK section to review the steps for supplying the provisioning file during SDK initialization.

  2. Add the Call Event History listeners. Refer to the Observing Events from SDK section to set your own Softphone Delegate for the SDK.

    The following code snippet shows how to register the onEventsChanged method of the SoftphoneDelegateBridge protocol for iOS:

     1extension AppDelegate: SoftphoneDelegateBridge {
     2
     3    // onEventChanged methods and onMissedCalls will be called by the SDK as this class is now the delegate of the SoftphoneBridge.
     4
     5    func onEventsChanged(events: SoftphoneChangedEvents!, streams: SoftphoneChangedStreams!) {
     6        // From here, you can trigger a refresh of the call history list in your app
     7
     8        // get changed events - only set if changedEvents.many is false
     9        let changedIds = events.eventIds // id's of changed events
    10        // check if many events changed, if true, changedIds is nil
    11        let manyChanged = events.many
    12        // check if any call event changed
    13        let callEventsChanged = streams.streamKeys.contains(StreamQuery.legacyCallHistoryStreamKey())
    14    }
    15}
    
  3. To display the call history in your app, perform a query to the device’s local storage and fetch all call events according to your filter and paging requirements, as shown in the following code snipet:

     1let query = SoftphoneQuery()
     2query.streamKey = SoftphoneStreamQuery.legacyCallHistoryStreamKey()
     3let missedCallValue = Int32(CallResultType_Missed.rawValue)
     4let attr = SoftphoneQueryAttr(key: "callResult", value: String(missedCallValue))
     5if let attr = attr {
     6    query.withAttributes = [attr]
     7}
     8
     9// optional paging for query results
    10let paging = SoftphonePaging()
    11/*paging.after = ...;
    12paging.before = ...;
    13paging.olderThan = ...;
    14paging.offset = ...;
    15paging.limit = ...;
    16etc...*/
    17
    18// fetch the call events
    19let fetchResult = SoftphoneBridge.instance().events().fetch(query, paging: paging)
    20
    21// get the missed call count
    22let missedCallsCount = fetchResult?.items.count ?? 0
    23
    24// get an array of missed calls
    25let missedCalls = fetchResult?.items.compactMap { item -> SoftphoneCallEvent? in
    26    guard let fetchItem = item as? SoftphoneFetchItem else { return nil }
    27    return fetchItem.event.asCall()
    28} ?? []
    

Handle Missed Calls

The SDK APIs make missed call management straightforward.

Displaying Missed Call Badge Count

To observe changes in the badge count, override the function onBadgeCountChanged() from the SoftphoneBadgeCountChangeDelegate.

1// Register self as the delegate for badge count changes
2SoftphoneBadgeManager.instance().registerBadgeCountDelegate(self)
3
4// Implement the callback method
5func onBadgeCountChanged() {
6    // Code to handle badge count change
7}

To get the badge count for the calls channel, follow these steps:

1// Create a badge address for the calls channel
2let badgeAddress = SoftphoneBadgeAddress(channel: SoftphoneBadgeAddress.calls)
3
4// Get the badge count for the specified address
5let missedCallCount = SoftphoneBadgeManager.instance().getBadgeCount(address: badgeAddress)

To set the badge count for the calls channel, use the following code:

1// Create a badge address for the calls channel
2let badgeAddress = SoftphoneBadgeAddress(channel: SoftphoneBadgeAddress.calls)
3
4// Set the badge count for the specified address to 3
5SoftphoneBadgeManager.instance().setBadgeCount(address: badgeAddress, count: 3)

To reset the badge count for the calls channel to zero, use the following code:

1// Create a badge address for the calls channel
2let badgeAddress = SoftphoneBadgeAddress(channel: SoftphoneBadgeAddress.calls)
3
4// Reset the badge count for the specified address
5SoftphoneBadgeManager.instance().resetBadgeCount(address: badgeAddress)

Displaying Missed Call Notifications

Use the simple onMissedCalls method to show notifications for missed calls in your app. This method is triggered when a new missed call is detected and only when the app is running.

The following code snippet shows how to override the onMissedCalls method of the SoftphoneDelegateBridge protocol and show a notification with the total missed call count on iOS:

 1extension AppDelegate: SoftphoneDelegateBridge {
 2
 3    func onMissedCalls(_ callEvents: [SoftphoneCallEvent]!) {
 4        for call in callEvents {
 5            showMissedCallNotification(call: call)
 6        }
 7    }
 8
 9    func showMissedCallNotification(call: SoftphoneCallEvent) {
10        let content = UNMutableNotificationContent()
11        content.title = call.getRemoteUser(index: 0).displayName
12        content.body = "Missed Call"
13
14        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
15
16        UNUserNotificationCenter.current().add(request) { error in
17            if let error = error {
18                debugPrint("Notification request error: \(error.localizedDescription)")
19            }
20        }
21    }
22}

Displaying the List of All Missed Calls

Refer to the previous Using Call Event History section to see how to retrieve the list of all missed calls.

Enabling Logging

Enable logging to capture important events and activities that occur within the SDK.

  • By default, the logger is disabled. To enable logging, set the trafficLogging preference to true, as shown in the following code snippet:

    1SoftphoneBridge.instance().getPreferences().trafficLogging = true
    
  • To disable logging, set the trafficLogging preference to false.

  • To direct the SDK logging into the iOS Console, define your own NSLogSerializer with a custom prefix (<your prefix>), as shown in the following code snippet:

    1let serializer = NSLogSerializer(prefix: "<your prefix>")
    2SoftphoneBridge.instance().log().setCustomSink(serializer)
    
  • To retrieve logs at any point, use the following code snippet:

    1let log = SoftphoneBridge.instance().log().get()
    2print(log)
    

Setting up for Push Notifications

Ensure you already set up the VoIP push notifications in both your project and server prior to enabling push notifications. If the setup is not completed yet, go to the iOS Push notifications section for detailed instructions.

The push notifications function is disabled by default in libSoftphone SDK. Modify the provisioning XML file to enable and use this function with the application.

Important

Ensure that your application correctly reports its current state to the SDK. This is crucial for the push notifications to work properly. See Reporting Application States for the setup.

  1. To set the SDK registration with the push servers, add the following snippet to the prefKeys node in the provisioning XML file.

     1<provisioning>
     2    <saas>
     3        <identifier>saas-identifier</identifier>
     4    </saas>
     5
     6    <prefKeys>
     7        <prop name="sipisDisabled" default="0"/>
     8        <prop name="pushNotificationsEnabled" default="1"/>
     9    </prefKeys>
    10</provisioning>
    

    This snippet is passed to the SoftphoneBridge.initialize method to enable push notifications when libSoftphone SDK initializes.

  2. Enable push notifications so that the application can receive calls. To do so, call the setRegistrationId method on the SoftphoneBridge.instance().notifications().push() instance. This method is used to pass the token data and the usage information to the SDK, as illustrated in the following code snippet:

    1func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
    2    SoftphoneBridge.instance().notifications().push().setRegistrationId(pushCredentials.token, usage: PushTokenUsage_IncomingCall)
    3}
    

Handling Push Notifications for Incoming Calls

After setting up the push notifications functions, pass the push notification payload to the SDK so that the SDK can handle push notifications for incoming calls. To do so, call the handle method on SoftphoneBridge.instance().notifications().push() instance, as illustrated in the following code snippet:

1func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
2    let xml = Dictionary<AnyHashable, Any>.xmlFromDictionary(payload.dictionaryPayload)
3    SoftphoneBridge.instance().notifications().push().handle(xml, usage: PushTokenUsage_IncomingCall, completion: nil)
4}