Getting Started with iOS Facade

Acrobits Facade SDK contains classes and methods that makes using the native libSoftphone SDK easier. This SDK simplifies the initialization process, configuration process, as well as the handling the push notifications and application lifetime events.

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

Adding Facade SDK 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 Facade Swift package https://github.com/acrobits/DeliveryHeroSoftphoneFacade.git.

  5. Select the Add Package button.

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

Initializing the SDK

Use the SoftphoneFacade.initialize(licenseKey:configuration:) method to initialize Facade SDK. The initialization process follows the builder pattern and allows you to configure the SDK using various callbacks that are lazily evaluated when needed, after activating the SDK with the SoftphoneFacade/start() method.

Initialization is fast and can be done even at app launch time due to the ability of lazily evaluation. Therefore, Acrobits recommends developers to initialize the SDK in the application(_:didFinishLaunchingWithOptions:) method of your application.

The following code snippet shows an example of how to initialize Facade SDK with various configurations. Replace certain values for your specific implementation.

 1SoftphoneFacade.instance.initialize(licenseKey: [[ replace with your license key ]], configuration: {
 2    (confBuilder : ConfigurationBuilder) -> Void in
 3    confBuilder.account(account: {(accountBuilder: AccountBuilder) -> Void in
 4// provide account information
 5        let xml = XmlTree(name: "account")!
 6        xml.setAttribute(name: "id", value: "acrobits")
 7        xml.setNodeValue(value: "1056", name: "username")
 8        xml.setNodeValue(value: "secret", name: "password")
 9        xml.setNodeValue(value: "host.mypbx.com", name: "host")
10// there should be only a single account at all times
11        accountBuilder.setSingle(xml: xml)
12
13    }).preferences(preferences: {(prefsBuilder: PreferencesBuilder) -> Void in
14// override the defaults for read-only preference keys
15        prefsBuilder.setOverrideDefaultsCallback(callback: {(prefs: SoftphonePreferences) -> Void in
16            prefs.overrideSipisDisabled(false)
17            prefs.overrideDefaultPushNotificationsEnabled(true)
18            prefs.overrideDefaultUserAgentOverride("deliveryHero")
19            prefs.overrideDefaultMaxNumber(ofConcurrentCalls: 1)
20        })
21// set values for read-write preference keys
22        prefsBuilder.setModifyPreferencesCallback(callback: {(prefs: SoftphonePreferences) -> Void in
23            prefs.trafficLogging = true
24        })
25    }).callAssets(callAssets: {(callAssets: CallAssetsBuilder) -> Void in
26// enhance the contact information with a display name (overriding the display name arriving in the incoming SIP INVITE) and an avatar
27        callAssets.setContactInfoCallback(callback: {(call: SoftphoneCallEvent) -> ContactInfo in
28            let contactInfo = ContactInfo(displayName: "John Doe", avatarUrl: URL(string: "https://images.unsplash.com/photo-1596285508507-5da6bec59433?crop=entropy&cs=tinysrgb&fm=jpg&ixlib=rb-1.2.1&q=80&raw_url=true&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=270")!)
29            return contactInfo
30        })
31        // return the name of bundle ringtone
32        // default ringtone will be played if
33        // - the callback is not set
34        // - empty string is returned
35        // - the ringtone is not in the app bundle
36        callAssets.setRingtoneCallback {
37            return "relax_ringtone.mp3"
38        }
39    }).localizable { localizableAsset in
40// return your custom localizable class derived from LocalizableImpl or a custom class implementing Localizable interface
41        localizableAsset.setLocalizableCallback {
42            let appLocalizable = AppLocalizable()
43            return appLocalizable
44        }
45    }.backButtonPolicy { policy in
46        policy.setBackButtonActionCallBack {
47            // possible actions are dismiss and endCall
48            // default is set to endCall
49            return .dismiss
50        }
51        policy.setBackButtonVisibleCallBack {
52            // return true to show back button
53            // return false to hide back button
54            // default is set to true
55            return true
56        }
57    }
58})

Note

  • Replace [[ replace with your license key ]] with a valid license key obtained in the Acrobits licensing portal. This key is required for initializing SoftphoneFacade. See step 1 in the Quick Start Guide.

  • To view all the recognized XML nodes and values for SIP account configurations, go to the Account XML documentation at doc.acrobits.net/cloudsoftphone/account.html#account-xml.

Registering for Push Notifications

After configuring the SDK, proceed to register for push notifications. Once the SDK registers for push notifications, the application can receive both remote notifications, such as from missed calls, and/or PushKit VoIP notifications, which are from incoming calls even when the app is not actively running in the foreground.

Note

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

There are two options to register for push notifications:

  • Use the SoftphoneFacade/AppDelegateProxy-swift.class/registerForPushNotifications() method.

    This method registers the application to receive both remote notifications and PushKit VOIP notifications.

  • Register directly from your code.

    This method allows the developers to decide whether to receive remote notifications or PushKit VOIP notifications. To register for the desired notification type, use the setPushToken() method of the SoftphoneFacade to provide the push token obtained from the registration process.

The following code snippet gives an example of how to proxy the delegate method to push notifications in the AppDelegate:

 1func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
 2{
 3    SoftphoneFacade.instance.appDelegateProxy.didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: deviceToken)
 4}
 5
 6func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error)
 7{
 8    SoftphoneFacade.instance.appDelegateProxy.didFailToRegisterForRemoteNotificationsWithError(error: error)
 9}
10
11func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)
12{
13    SoftphoneFacade.instance.appDelegateProxy.didReceiveRemoteNotification(userInfo: userInfo, fetchCompletionHandler: completionHandler)
14}

Starting Up the SDK

After initializing the SDK via SoftphoneFacade/initialize(licenseKey:configuration:), call SoftphoneFacade/start() to activate Facade SDK. This invokes certain configuration callbacks, depending on the configuration. If a SIP account is provided in AccountBuilder, the SIP account registration process is initiated when starting the SDK.

  • To check the current status of the SDK, use SoftphoneFacade/state-swift.property().

  • To terminate the SDK, use SoftphoneFacade/terminate(_:).

The following code snippet gives an example of how to properly start the SDK:

 1override func viewDidLoad()
 2{
 3    super.viewDidLoad()
 4
 5    if SoftphoneFacade.state == .terminating
 6    {
 7        self.callButton.isEnabled = false
 8        infoLabel.text = "Waiting..."
 9        _ = SoftphoneFacade.instance.addTerminatingCallback({()->Void in
10            self.doStart()
11        })
12    } else if SoftphoneFacade.state != .running
13    {
14        self.doStart()
15    } else
16    {
17        // started from somewhere else - push handler for instance
18        SoftphoneFacade.instance.addNetworkStateDelegate(self)
19        SoftphoneFacade.instance.addSoftphoneDelegate(self)
20        SoftphoneFacade.instance.addCallRepositoryDelegate(self)
21
22        guard let state = SoftphoneFacade.instance.getRegistrationState(index: 0) else
23        {
24            return
25        }
26
27        infoLabel.text = RegistratorState.toString(state)
28    }
29}
30
31func doStart()
32{
33    SoftphoneFacade.instance.start()
34    SoftphoneFacade.instance.addNetworkStateDelegate(self)
35    SoftphoneFacade.instance.addSoftphoneDelegate(self)
36    SoftphoneFacade.instance.addCallRepositoryDelegate(self)
37    self.callButton.isEnabled = true
38}
39
40deinit
41{
42    SoftphoneFacade.instance.removeSofthoneDelegate(self)
43    SoftphoneFacade.instance.removeNetworkStateDelegate(self)
44    SoftphoneFacade.instance.removeCallRepositoryDelegate(self)
45
46    if SoftphoneFacade.state == .running
47    {
48        _ = SoftphoneFacade.instance.terminate(nil)
49    }
50}

Reporting Lifecycle

Facade SDK automatically handles the application’s lifecycle management. Hence, developers do not have to take any specific actions regarding the lifecycle management of Facade or the underlying libSoftphone SDK.

Once started, the SDK automatically observes lifecycle events of the UIScene (user interface scene) to detect application transitions between foreground and background states. Based on these observations, the SDK ensures that SIP registration is synchronized with the backend accordingly.

Terminating and Restarting the SDK

By using the termination and callback mechanisms, Facade SDK starts and terminates at the appropriate times during the app’s runtime.

  • Use the SoftphoneFacade/terminate(_:) method to terminate the SDK.

  • To determine whether the SDK is currently running or in the process of terminating, use the SoftphoneFacade/isRunning() and SoftphoneFacade/isTerminating() methods.

  • If you expect the need to start and terminate the SDK multiple times during app runtime, such as having the SDK available only when specific view controllers are instantiated, ensure to start the SDK only after it is terminated properly.

  • There are two options to enable notifications when the SDK is completely terminated:

    • Set a callback function when calling SoftphoneFacade/terminate(_:).

    • Use the SoftphoneFacade/addTerminatingCallback(_:) method to set a callback in the code.

Placing Outgoing Calls

  1. Prior to making outgoing calls, initialize the SDK by using SoftphoneFacade/initialize(licenseKey:configuration:) and then start the SDK by using SoftphoneFacade/start() or SoftphoneFacade/ensureStarted().

  2. To place a call, use the SoftphoneFacade/placeCall(_:) method.

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

 1@IBAction func startCallButtonTapped()
 2{
 3
 4    if SoftphoneFacade.instance.getNetwork() == NetworkType_None
 5    {
 6        // there's no network - we cannot make the call
 7        presentNoNetworkScreen()
 8        return
 9    }
10
11    if SoftphoneFacade.instance.hasActiveCall()
12    {
13        // the call is already in progress - just show the in call screen
14        self.presentCallScreen()
15        return
16    }
17
18    if SoftphoneFacade.instance.placeCall("1078")
19    {
20        // show some call-in-progress animation and then move to the in-call screen
21        displayPreCallAnimation()
22        DispatchQueue.main.asyncAfter(deadline: .now() + 3)
23        {
24            self.presentCallScreen()
25        }
26    }
27}
28
29private func presentCallScreen()
30{
31    let callVC = SoftphoneFacade.instance.callViewController
32
33    if self.navigationController?.presentedViewController != nil { return }
34
35    callVC.modalTransitionStyle = .crossDissolve
36    callVC.modalPresentationStyle  = .fullScreen
37
38    present(callVC, animated: true)
39    {
40        self.preCallActivityView.isHidden = true
41    }
42}

Handling Push Notifications

The push notification registrations option determines whether the application receives incoming push notifications and/or remote notifications, as explained in Registering for Push Notifications. To enable the application to display an incoming call screen, when the application receives an incoming call notification, the SDK triggers appropriate delegate methods, such as the call(_:stateChangedTo:).

  • The SDK handles incoming call notifications internally when using SoftphoneFacade/AppDelegateProxy-swift.class/registerForPushNotifications() to register for push notifications. If you register push notifications directly in the code instead of through the SDK, to ensure the SDK processes the incoming call push notifications or remote notifications:

    • Incoming call push notifications - Proxy the notifications to the SoftphoneFacade/AppDelegateProxy-swift.class/pushRegistry(_:didUpdate:for:) method.

    • Remote notifications - Proxy the notification to their to their respective SoftphoneFacade/AppDelegateProxy-swift.class methods.

  • To enable the SoftphoneFacade to display incoming call screen even if the SDK is not started yet, such as when the SDK is only started in specific view controllers:

    1. Implement the SoftphoneDelegate protocol.

    2. Register the SoftphoneDelegate protocol with SoftphoneFacade/addSoftphoneDelegate(_:).

    3. When you receive a call through SoftphoneDelegate/call(_:stateChangedTo:), check if the call state is CallState_Established or CallState_IncomingAnswered.

    4. If the call state matches the desired conditions, present the SoftphoneFacade/callViewController from the current location in your app.

The following snippet code demonstrates how the SDK handles push notifications to present the incoming call screen when receiving calls:

 1override func viewDidLoad()
 2{
 3    super.viewDidLoad()
 4    SoftphoneFacade.instance.addSoftphoneDelegate(self)
 5}
 6
 7deinit
 8{
 9    SoftphoneFacade.instance.removeSofthoneDelegate(self)
10}
11
12func call(_ call: SoftphoneCallEvent, stateChangedTo state: CallStateType)
13{
14    guard state == CallState_Established || state == CallState_IncomingAnswered else
15    {
16        return
17    }
18
19    let callVC = SoftphoneFacade.instance.callViewController
20
21    if self.navigationController?.topViewController != self { return }
22
23    callVC.modalTransitionStyle = .crossDissolve
24    callVC.modalPresentationStyle  = .fullScreen
25
26    present(callVC, animated: true)
27}

Observing Events from the SDK

To observe events related to account registration and calls in the application, implement the SoftphoneDelegate protocol and register it with with Facade using SoftphoneFacade/addSoftphoneDelegate(_:).

Here are the event-related methods to implement in the SoftphoneDelegate:

 1// This will be called when the registration state changes.
 2func siphoneStateChanged(_ state: RegistratorStateType)
 3
 4// This will be called when the state of the call changes. (e.g. from Trying to Ringing)
 5func callStateChanged(state: CallStateType, call: SoftphoneCallEvent)
 6
 7// This will be called when the call is put on hold or unheld
 8func holdStatesChanged(states: CallHoldStates, call: SoftphoneCallEvent)
 9
10// This will be called when there is an incoming call or message
11func newEvent(_ event: SoftphoneEvent)

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 from the preferences block while initializing the SoftphoneFacade, as shown in the following code snippet:

    1confBuilder.preferences(preferences: {(prefsBuilder: PreferencesBuilder) -> Void in
    2        prefsBuilder.setModifyPreferencesCallback(callback: {(prefs: SoftphonePreferences) -> Void in
    3                    prefs.trafficLogging = true
    4                })
    5            })
    
  • To disable logging, set the trafficLogging preference to false in the preferences block.

  • To direct the SDK logging into the iOS Console, define your own NSLogSerializer with a custom prefix (<your prefix>), and then assign it as SoftphoneFacade’s customSink object, as shown in the following code snippet:

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

    1let log = SoftphoneFacade.instance.getLogger().get()
    2print(log)
    

Accessing Telemetry Information

You can access telemetry information through Facade SDK and register to receive specific or all telemetry events.

There are two ways to register for telemetry events:

  • To register for specific events, specify the type using the .on(AnyClass, TelemetryEventHandler) function. All the events inherit from a common TelemetryEvent interface.

  • To listen for all events, use the .onAny(TelmetryEventHandler) function.

The following snippet code demonstrates examples of using these functions to log events:

1// In order to get all the events we need to use onAny method of telemetry provider
2SoftphoneFacade.instance.telemetryProvider.onAny { event in
3    print(event.toString())
4}
5
6// In order to get a specific event (e.g. InAppCallErrorCaused), we need to specify the class
7SoftphoneFacade.instance.telemetryProvider.on(InAppCallErrorCaused.self) { event in
8    print(event.toString())
9}

All the events also implement the .toString() method to convert an event object to its string representation.

Customizing Localization

Developers can customize the localized strings for the UI elements used in Facade SDK.

There are three ways to customize the localization of the SDK:

  • Create a custom class that implements the Localizable protocol provided by the SDK. The following snippet code gives an example of the muteButtonTitle property is overridden with a custom value of "Mute".

    1class AppLocalizable: Localizable {
    2    var muteButtonTitle: String = "Mute"
    3    ...
    4}
    
  • Derive a custom class from the LocalizableImpl class, the default implementation of the Localizable protocol, provided by the SDK. By doing so, you can selectively override specific properties that you want to change while keeping the rest of the localization values intact. The following snippet code gives an example.

    1class AppLocalizable: Localizable {
    2    var muteButtonTitle: String = "Mute"
    3    ...
    4}
    
  • Add an overrides string entry in the Localizable.strings file of the SDK. The SDK first checks for the translation of the key in the host app bundle. If the key does not exist, the translation in the Localizable.strings file is used.