===================================== Getting Started with iOS libSoftphone ===================================== This section describes the steps to create an iOS application that can place and receive calls using **libSoftphone SDK**. .. contents:: :local: :depth: 1 -------------------------------- Adding SoftphoneSwift Dependency -------------------------------- To add a package dependency to the Xcode project: .. include:: quick-start.rst :start-after: .. _ios-adding-libsoftphone-dependency-start: :end-before: .. _ios-adding-libsoftphone-dependency-end: 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. .. code-block:: groovy :linenos: let license = """ your license key """ // This method is used for initializing an instance of SDK SoftphoneBridge.initialize(license) .. note:: Replace **your license key** between the ```` 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 :ref:`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: .. code-block:: groovy :linenos: func applicationDidBecomeActive(_ application: UIApplication) { let bg = UIApplication.shared.applicationState == .background SoftphoneBridge.instance().state().update(bg ? InstanceState_Background : InstanceState_Active) } func applicationWillResignActive(_ application: UIApplication) { SoftphoneBridge.instance().state().update(InstanceState_Inactive) } func applicationDidEnterBackground(_ application: UIApplication) { SoftphoneBridge.instance().state().update(InstanceState_Background) } func applicationWillEnterForeground(_ application: UIApplication) { SoftphoneBridge.instance().state().update(InstanceState_Active) } -------------------- 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: .. code-block:: :linenos: let xml = XmlTree(name: "account") xml?.setAttribute(name: "id", value: "sip") xml?.setNodeValue(value: "Sip Account", name: "title") xml?.setNodeValue(value: "1125", name: "username") xml?.setNodeValue(value: "misscom", name: "password") xml?.setNodeValue(value: "pbx.acrobits.cz", name: "host") xml?.setNodeValue(value: "udp", name: "transport") SoftphoneBridge.instance().registration().saveAccount(xml) SoftphoneBridge.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. .. code-block:: :linenos: let sipAccount = """ Sip Account 1125 misscom pbx.acrobits.cz udp """ let xml = XmlTree.parse(sipAccount) SoftphoneBridge.instance().registration().saveAccount(xml) SoftphoneBridge.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. .. code-block:: :linenos: SoftphoneBridge.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: .. code-block:: groovy :linenos: let softphoneObserverProxy = SoftphoneObserverProxyBridge() softphoneObserverProxy.delegate = self; SoftphoneBridge.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: .. code-block:: groovy :linenos: // This delegate will be called when the registration state changes func onRegistrationStateChanged(state: RegistratorStateType, accountId: String!) { } // This delegate will be called when a new event arrives (i.e. incoming call/message) func onNewEvent(_ event: SoftphoneEvent!) { } // This delegate will be called when the state of the call changes func onCallStateChanged(state: CallStateType, call: SoftphoneCallEvent!) { } // This delegate will be called when the network change is detected func onNetworkChangeDetected(_ network: NetworkType) { } // This delegate will be called when the hold state of a call is changed func onHoldStateChanged(states: CallHoldStates!, call: SoftphoneCallEvent!) { } * 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. .. code-block:: groovy :linenos: Softphone_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: .. code-block:: :linenos: func call(number: String) { if number.isEmpty { return; } let call = SoftphoneCallEvent.create(withAccountId: "sip", uri: number) let stream = SoftphoneEventStream.load(SoftphoneStreamQuery.legacyCallHistoryStreamKey()) call?.setStream(stream) call?.transients.set("voiceCall", forKey: "dialAction") let result = SoftphoneBridge.instance().events().post(call) if result != PostResult_Success { print("Failed to post call event") } } -------------------- 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: .. code-block:: :linenos: if SoftphoneBridge.instance().settings().getPreferences().trafficLogging { print("SIP traffic logging is enabled") } ^^^^^^^^^^^^^^^^^^^^ 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: .. code-block:: :linenos: SoftphoneBridge.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. #. To enable the Call Event History feature, add the following code in the app's provisioning XML file: .. code-block:: groovy :linenos: your license key You can refer to the `Initializing the SDK`_ section to review the steps for supplying the provisioning file during SDK initialization. #. 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: .. code-block:: groovy :linenos: extension AppDelegate: SoftphoneDelegateBridge { // onEventChanged methods and onMissedCalls will be called by the SDK as this class is now the delegate of the SoftphoneBridge. func onEventsChanged(events: SoftphoneChangedEvents!, streams: SoftphoneChangedStreams!) { // From here, you can trigger a refresh of the call history list in your app // get changed events - only set if changedEvents.many is false let changedIds = events.eventIds // id's of changed events // check if many events changed, if true, changedIds is nil let manyChanged = events.many // check if any call event changed let callEventsChanged = streams.streamKeys.contains(StreamQuery.legacyCallHistoryStreamKey()) } } #. 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: .. code-block:: groovy :linenos: let query = SoftphoneQuery() query.streamKey = SoftphoneStreamQuery.legacyCallHistoryStreamKey() query.resultMask = CallEvent.Result.missed // optional paging for query results let paging = SoftphonePaging() /*paging.after = ...; paging.before = ...; paging.olderThan = ...; paging.offset = ...; paging.limit = ...; etc...*/ // fetch the call events let result = SoftphoneBridge.instance().events().fetch(query, paging: paging) // get the missed call count let missedCallsCount = fetchResult?.items.count ?? 0 // get an array of missed calls let missedCalls = fetchResult?.items.compactMap { item -> SoftphoneCallEvent? in guard let fetchItem = item as? SoftphoneFetchItem else { return nil } return fetchItem.event.asCall() } ?? [] ------------------- Handle Missed Calls ------------------- The SDK APIs make missed call management straightforward. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Displaying Missed Call Badge Count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use ``SoftphoneBridge.instance().notifications().getMissedCallCount()`` to retrieve the current missed call badge count. The app can display the missed call badge count on the app icon or in the app, such as in the Dialer tab. Use ``SoftphoneBridge.instance().notifications().resetMissedCallCount()`` to clear the missed call badge count, for example, after the user reviews the missed call history. To observe changes in the missed call badge count, override the function ``onBadgeCountChanged()`` from the ``SoftphoneDelegateBridge``. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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: .. code-block:: groovy :linenos: extension AppDelegate: SoftphoneDelegateBridge { func onMissedCalls(_ callEvents: [Any]!) { showMissedCallCountNotification(count: getAllMissedCalls()) } private func showMissedCallCountNotification(count: Int) { let content = UNMutableNotificationContent() content.title = "Missed Calls: \(count)" let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil) UNUserNotificationCenter.current().add(request) { error in if let error = error { debugPrint("Notification request error: \(error.localizedDescription)") } } } } ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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: .. code-block:: :linenos: SoftphoneBridge.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 (````), as shown in the following code snippet: .. code-block:: :linenos: let serializer = NSLogSerializer(prefix: "") SoftphoneBridge.instance().log().setCustomSink(serializer) * To retrieve logs at any point, use the following code snippet: .. code-block:: :linenos: let log = SoftphoneBridge.instance().log().get() print(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 :ref:`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. .. code-block:: groovy :linenos: saas-identifier 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: .. code-block:: groovy :linenos: func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { SoftphoneBridge.instance().notifications().push().setRegistrationId(pushCredentials.token, usage: PushTokenUsage_IncomingCall) } ---------------------------------------------- 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: .. code-block:: :linenos: func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) { let xml = Dictionary.xmlFromDictionary(payload.dictionaryPayload) SoftphoneBridge.instance().notifications().push().handle(xml, usage: PushTokenUsage_IncomingCall, completion: nil) }