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 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()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:
@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.classmethods. - To enable the
SoftphoneFacadeto 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
SoftphoneDelegateprotocol. - Register the
SoftphoneDelegateprotocol withSoftphoneFacade/addSoftphoneDelegate(_:). - When you receive a call through
SoftphoneDelegate/call(_:stateChangedTo:), check if the call state isCallState_EstablishedorCallState_IncomingAnswered. - If the call state matches the desired conditions, present the
SoftphoneFacade/callViewControllerfrom 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
trafficLoggingpreference totruefrom thepreferencesblock while initializing theSoftphoneFacade, 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
trafficLoggingpreference tofalsein thepreferencesblock. -
To direct the SDK logging into the iOS Console, define your own
NSLogSerializerwith a custom prefix (<your prefix>), and then assign it asSoftphoneFacade'scustomSinkobject, 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 commonTelemetryEventinterface. - 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
Localizableprotocol provided by the SDK. The following snippet code gives an example of themuteButtonTitleproperty is overridden with a custom value of"Mute".
class AppLocalizable: Localizable {
var muteButtonTitle: String = "Mute"
...
}
- Derive a custom class from the
LocalizableImplclass, the default implementation of theLocalizableprotocol, 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.stringsfile 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.stringsfile is used.