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:
Open your Xcode project.
Select the Package Dependencies tab.
Select the + button.
In the Enter Package URL field, add the URL of the Facade Swift package https://github.com/acrobits/DeliveryHeroSoftphoneFacade.git.
Select the Add Package button.
After adding the dependency, Xcode automatically includes the
Softphone
andSoftphone_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 theSoftphoneFacade
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()
andSoftphoneFacade/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¶
Prior to making outgoing calls, initialize the SDK by using
SoftphoneFacade/initialize(licenseKey:configuration:)
and then start the SDK by usingSoftphoneFacade/start()
orSoftphoneFacade/ensureStarted()
.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:
Implement the
SoftphoneDelegate
protocol.Register the
SoftphoneDelegate
protocol withSoftphoneFacade/addSoftphoneDelegate(_:)
.When you receive a call through
SoftphoneDelegate/call(_:stateChangedTo:)
, check if the call state isCallState_Established
orCallState_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:
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 totrue
from thepreferences
block while initializing theSoftphoneFacade
, 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 tofalse
in thepreferences
block.To direct the SDK logging into the iOS Console, define your own
NSLogSerializer
with a custom prefix (<your prefix>
), and then assign it asSoftphoneFacade
’scustomSink
object, as shown in the following code snippet:1let serializer = NSLogSerializer(prefix: "<your prefix>") 2SoftphoneFacade.instance.customSink = serializerTo 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 commonTelemetryEvent
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 themuteButtonTitle
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 theLocalizable
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 theLocalizable.strings
file is used.