﻿# Getting Started with iOS libSoftphone

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

## Adding SoftphoneSwift 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 libSoftphone Swift package <https: github.com acrobits softphoneswiftpackage-saas.git>.
5. Select the **Add Package** button.

After adding the dependency, Xcode automatically includes the `Softphone` dependencies.

## Configuring iOS Capabilities

Add the following capabilities to your app target in Xcode:

1.  Open **Targets → YourApp → Signing & Capabilities**.

2.  Click **+ Capability** and add **Background Modes**.

3.  Under **Background Modes**, enable:

    - **Audio**
    - **Voice over IP (VoIP)**

!!! note
    These capabilities ensure your app can run audio in the background and handle VoIP behavior required for incoming and outgoing calls.

## 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.

```groovy
let license = """
    <root>
        <saas>
            <identifier>your license key</identifier>
        </saas>
    </root>
    """

// This method is used for initializing an instance of SDK
SoftphoneBridge.initialize(license)  
```

!!! note
    Replace **your license key** between the `<identifier>` 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 [Quick Start Guide](quick-start.md#libsoftphone-licensing-portal-registration).

## 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](../../frameworks/account/account.md#account-xml) documentation.

### 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:

  ```
  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)
  ```

- 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.

  ```
  let sipAccount = """
      <account id="\"sip\"">
          <title>Sip Account</title>
          <username>1125</username>
          <password>misscom</password>
          <host>pbx.acrobits.cz</host>
          <transport>udp</transport>
      </account>
  """

  let xml = XmlTree.parse(sipAccount)
  SoftphoneBridge.instance().registration().saveAccount(xml)
  ```

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.

```
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:

  ```groovy
  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:

  ```groovy
  // 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.

  ```groovy
  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:

```
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)

    if let callTransients = call?.transients {
        callTransients.set("voiceCall", forKey: "dialAction")
        call?.transients = callTransients
    }

    let result = SoftphoneBridge.instance().events().post(call)

    if result != PostResult_Success {
        print("Failed to post call event")
    }
}
```

!!! warning
    The iOS Simulator does not support the **CallKit framework**. Due to this limitation, SIP calls will only function while the app is in the foreground.

## 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:

```
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:

```
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.

1.  To enable the Call Event History feature, add the following code in the app's provisioning XML file:

    ```groovy
    <root>
        <saas>
            <identifier>your license key</identifier>
        </saas>
        <prefkeys>
            <prop name="historyStorageType" default="sql"></prop>
        </prefkeys>
    </root>
    ```

    You can refer to the [Initializing the SDK](#initializing-the-sdk) section to review the steps for supplying the provisioning file during SDK initialization.

2.  Add the Call Event History listeners. Refer to the [Observing Events from SDK](#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:

    ```groovy
    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())
        }
    }
    ```

3.  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:

    ```groovy
    let query = SoftphoneQuery()
    query.streamKey = SoftphoneStreamQuery.legacyCallHistoryStreamKey()
    let missedCallValue = Int32(CallResultType_Missed.rawValue)
    let attr = SoftphoneQueryAttr(key: "callResult", value: String(missedCallValue))
    if let attr = attr {
        query.withAttributes = [attr]
    }

    // optional paging for query results
    let paging = SoftphonePaging()
    /*paging.after = ...;
    paging.before = ...;
    paging.olderThan = ...;
    paging.offset = ...;
    paging.limit = ...;
    etc...*/

    // fetch the call events
    let fetchResult = 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

To observe changes in the badge count, override the function `onBadgeCountChanged()` from the `SoftphoneBadgeCountChangeDelegate`.

```groovy
// Register self as the delegate for badge count changes
SoftphoneBadgeManager.instance().registerBadgeCountDelegate(self)

// Implement the callback method
func onBadgeCountChanged() {
    // Code to handle badge count change
}
```

To get the badge count for the `calls` channel, follow these steps:

```groovy
// Create a badge address for the calls channel
let badgeAddress = SoftphoneBadgeAddress(channel: SoftphoneBadgeAddress.calls)

// Get the badge count for the specified address
let missedCallCount = SoftphoneBadgeManager.instance().getBadgeCount(address: badgeAddress)
```

To set the badge count for the `calls` channel, use the following code:

```groovy
// Create a badge address for the calls channel
let badgeAddress = SoftphoneBadgeAddress(channel: SoftphoneBadgeAddress.calls)

// Set the badge count for the specified address to 3
SoftphoneBadgeManager.instance().setBadgeCount(address: badgeAddress, count: 3)
```

To reset the badge count for the `calls` channel to zero, use the following code:

```groovy
// Create a badge address for the calls channel
let badgeAddress = SoftphoneBadgeAddress(channel: SoftphoneBadgeAddress.calls)

// Reset the badge count for the specified address
SoftphoneBadgeManager.instance().resetBadgeCount(address: badgeAddress)
```

### 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:

```groovy
extension AppDelegate: SoftphoneDelegateBridge {

    func onMissedCalls(_ callEvents: [SoftphoneCallEvent]!) {
        for call in callEvents {
            showMissedCallNotification(call: call)
        }
    }

    func showMissedCallNotification(call: SoftphoneCallEvent) {
        let content = UNMutableNotificationContent()
        content.title = call.getRemoteUser(index: 0).displayName
        content.body = "Missed Call"

        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](#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`:

```swift
SoftphoneBridge.instance().getPreferences().trafficLogging = true
```

To disable logging, set the `trafficLogging` preference to `false`.

To direct SDK logs to the iOS Console, define an `NSLogSerializer` with a custom prefix:

```swift
let serializer = NSLogSerializer(prefix: "<your prefix>")
SoftphoneBridge.instance().log().setCustomSink(serializer)
```

### Adding a Custom Log Consumer

If you need more control over log handling, you can provide a custom sink. The following example shows a serializer that writes log entries to a file and also prints them to the iOS Console.

```swift
let serializer = CustomLogSerializer(
    fileURL: FileManager.default.temporaryDirectory.appendingPathComponent("app.log")
)
SoftphoneBridge.instance().log().setCustomSink(serializer)
```

```swift
import Foundation
import Softphone_Swift

final class CustomLogSerializer: NSObject, Serializer {
    private let fileURL: URL

    init?(fileURL: URL) {
        self.fileURL = fileURL

        let fm = FileManager.default
        if !fm.fileExists(atPath: fileURL.path) {
            fm.createFile(atPath: fileURL.path, contents: nil)
        }
    }

    func write(_ data: Data) -> Int {
        do {
            let handle = try FileHandle(forWritingTo: fileURL)
            defer { try? handle.close() }

            try handle.seekToEnd()
            try handle.write(contentsOf: data)

            let string = String(data: data, encoding: .utf8) ?? ""
            NSLog("%@", string)

            return data.count
        } catch {
            return -1
        }
    }

    func availToWrite() -> Int {
        0x7FFFFFFF
    }
}
```

To retrieve logs at any point, use the following code:

```swift
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 [iOS Push notifications](../pushes/ios.md#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.

1.  To set the SDK registration with the push servers, add the following snippet to the `prefKeys` node in the provisioning XML file.

    ```groovy
    <provisioning>
        <saas>
            <identifier>saas-identifier</identifier>
        </saas>
    
        <prefkeys>
            <prop name="sipisDisabled" default="0"></prop>
            <prop name="pushNotificationsEnabled" default="1"></prop>
        </prefkeys>
    </provisioning>
    ```
    
    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:

    ```groovy
    func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
        SoftphoneBridge.instance().notifications().push().setRegistrationId(pushCredentials.token, usage: PushTokenUsage_IncomingCall)
    }
    ```

!!! warning
    The Simulator does **not** support PushKit (VoIP) push notifications. SIP calls will only work while the app is in the **foreground**. For full push/call behavior (including CallKit), test on a physical iOS device.

## 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:

```
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
    let xml = XmlTree.from(dictionary: payload.dictionaryPayload)
    SoftphoneBridge.instance().notifications().push().handle(xml, usage: PushTokenUsage_IncomingCall, completion: nil)
}
```