Skip to content

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.

SoftphoneFacade.instance.initialize(licenseKey: [[ replace with your license key ]], configuration: {
    (confBuilder : ConfigurationBuilder) -> Void in
    confBuilder.account(account: {(accountBuilder: AccountBuilder) -> Void in
// provide account information
        let xml = XmlTree(name: "account")!
        xml.setAttribute(name: "id", value: "acrobits")
        xml.setNodeValue(value: "1056", name: "username")
        xml.setNodeValue(value: "secret", name: "password")
        xml.setNodeValue(value: "host.mypbx.com", name: "host")
// there should be only a single account at all times        
        accountBuilder.setSingle(xml: xml)

    }).preferences(preferences: {(prefsBuilder: PreferencesBuilder) -> Void in
// override the defaults for read-only preference keys
        prefsBuilder.setOverrideDefaultsCallback(callback: {(prefs: SoftphonePreferences) -> Void in
            prefs.overrideSipisDisabled(false)
            prefs.overrideDefaultPushNotificationsEnabled(true)
            prefs.overrideDefaultUserAgentOverride("deliveryHero")
            prefs.overrideDefaultMaxNumber(ofConcurrentCalls: 1)
        })
// set values for read-write preference keys 
        prefsBuilder.setModifyPreferencesCallback(callback: {(prefs: SoftphonePreferences) -> Void in
            prefs.trafficLogging = true
        })
    }).callAssets(callAssets: {(callAssets: CallAssetsBuilder) -> Void in
// enhance the contact information with a display name (overriding the display name arriving in the incoming SIP INVITE) and an avatar
        callAssets.setContactInfoCallback(callback: {(call: SoftphoneCallEvent) -> ContactInfo in
            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")!)
            return contactInfo
        })
        // return the name of bundle ringtone
        // default ringtone will be played if 
        // - the callback is not set 
        // - empty string is returned 
        // - the ringtone is not in the app bundle
        callAssets.setRingtoneCallback {
            return "relax_ringtone.mp3"
        }
    }).localizable { localizableAsset in
// return your custom localizable class derived from LocalizableImpl or a custom class implementing Localizable interface
        localizableAsset.setLocalizableCallback {
            let appLocalizable = AppLocalizable()
            return appLocalizable
        }
    }.backButtonPolicy { policy in
        policy.setBackButtonActionCallBack {
            // possible actions are dismiss and endCall
            // default is set to endCall
            return .dismiss
        }
        policy.setBackButtonVisibleCallBack {
            // return true to show back button
            // return false to hide back button
            // default is set to true
            return true
        }
    }
})

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.

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:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
{
    SoftphoneFacade.instance.appDelegateProxy.didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: deviceToken)
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error)
{
    SoftphoneFacade.instance.appDelegateProxy.didFailToRegisterForRemoteNotificationsWithError(error: error)
}

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)
{        
    SoftphoneFacade.instance.appDelegateProxy.didReceiveRemoteNotification(userInfo: userInfo, fetchCompletionHandler: completionHandler)
}

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:

override func viewDidLoad()
{
    super.viewDidLoad()

    if SoftphoneFacade.state == .terminating
    {
        self.callButton.isEnabled = false
        infoLabel.text = "Waiting..."
        _ = SoftphoneFacade.instance.addTerminatingCallback({()->Void in
            self.doStart()
        })
    } else if SoftphoneFacade.state != .running
    {
        self.doStart()
    } else
    {
        // started from somewhere else - push handler for instance
        SoftphoneFacade.instance.addNetworkStateDelegate(self)
        SoftphoneFacade.instance.addSoftphoneDelegate(self)
        SoftphoneFacade.instance.addCallRepositoryDelegate(self)

        guard let state = SoftphoneFacade.instance.getRegistrationState(index: 0) else
        {
            return
        }

        infoLabel.text = RegistratorState.toString(state)
    }
}

func doStart()
{
    SoftphoneFacade.instance.start()
    SoftphoneFacade.instance.addNetworkStateDelegate(self)
    SoftphoneFacade.instance.addSoftphoneDelegate(self)
    SoftphoneFacade.instance.addCallRepositoryDelegate(self)
    self.callButton.isEnabled = true        
}

deinit
{
    SoftphoneFacade.instance.removeSofthoneDelegate(self)
    SoftphoneFacade.instance.removeNetworkStateDelegate(self)
    SoftphoneFacade.instance.removeCallRepositoryDelegate(self)

    if SoftphoneFacade.state == .running
    {
        _ = SoftphoneFacade.instance.terminate(nil)
    }
}

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:

@IBAction func startCallButtonTapped()
{

    if SoftphoneFacade.instance.getNetwork() == NetworkType_None
    {
        // there's no network - we cannot make the call
        presentNoNetworkScreen()
        return
    }

    if SoftphoneFacade.instance.hasActiveCall()
    {
        // the call is already in progress - just show the in call screen
        self.presentCallScreen()
        return
    }

    if SoftphoneFacade.instance.placeCall("1078")
    {
        // show some call-in-progress animation and then move to the in-call screen
        displayPreCallAnimation()
        DispatchQueue.main.asyncAfter(deadline: .now() + 3)
        {
            self.presentCallScreen()
        }
    }
}

private func presentCallScreen()
{
    let callVC = SoftphoneFacade.instance.callViewController

    if self.navigationController?.presentedViewController != nil { return }

    callVC.modalTransitionStyle = .crossDissolve
    callVC.modalPresentationStyle  = .fullScreen

    present(callVC, animated: true)
    {
        self.preCallActivityView.isHidden = true
    }
}

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:
  • Implement the SoftphoneDelegate protocol.
  • Register the SoftphoneDelegate protocol with SoftphoneFacade/addSoftphoneDelegate(_:).
  • When you receive a call through SoftphoneDelegate/call(_:stateChangedTo:), check if the call state is CallState_Established or CallState_IncomingAnswered.
  • 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:

override func viewDidLoad()
{
    super.viewDidLoad()
    SoftphoneFacade.instance.addSoftphoneDelegate(self)
}

deinit
{
    SoftphoneFacade.instance.removeSofthoneDelegate(self)
}

func call(_ call: SoftphoneCallEvent, stateChangedTo state: CallStateType)
{
    guard state == CallState_Established || state == CallState_IncomingAnswered else
    {
        return
    }

    let callVC = SoftphoneFacade.instance.callViewController

    if self.navigationController?.topViewController != self { return }

    callVC.modalTransitionStyle = .crossDissolve
    callVC.modalPresentationStyle  = .fullScreen

    present(callVC, animated: true)
}

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:

// This will be called when the registration state changes.
func siphoneStateChanged(_ state: RegistratorStateType)

// This will be called when the state of the call changes. (e.g. from Trying to Ringing)
func callStateChanged(state: CallStateType, call: SoftphoneCallEvent)

// This will be called when the call is put on hold or unheld
func holdStatesChanged(states: CallHoldStates, call: SoftphoneCallEvent)

// This will be called when there is an incoming call or message
func 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:
confBuilder.preferences(preferences: {(prefsBuilder: PreferencesBuilder) -> Void in
        prefsBuilder.setModifyPreferencesCallback(callback: {(prefs: SoftphonePreferences) -> Void in
                    prefs.trafficLogging = true
                })
            })
  • 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:

let serializer = NSLogSerializer(prefix: "<your prefix>")
SoftphoneFacade.instance.customSink = serializer
  • To retrieve logs at any point, use the following code snippet:
let log = SoftphoneFacade.instance.getLogger().get()
print(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:

// In order to get all the events we need to use onAny method of telemetry provider
SoftphoneFacade.instance.telemetryProvider.onAny { event in
    print(event.toString())
}

// In order to get a specific event (e.g. InAppCallErrorCaused), we need to specify the class
SoftphoneFacade.instance.telemetryProvider.on(InAppCallErrorCaused.self) { event in
    print(event.toString())
}

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".
class AppLocalizable: Localizable {
    var muteButtonTitle: String = "Mute"
    ...
}
  • 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.
class AppLocalizable: Localizable {
    var muteButtonTitle: String = "Mute"
    ...
}
  • 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.