Integrate the SecuredCalls Voice SDK
Add branded, verified in-app voice calling to your iOS app. Follow these sections to install the SDK, configure your project, initialize the SDK, place calls, and handle history, missed-call callbacks, and biometric verification.
Estimated time: 40 minutes
Overview
The SecuredCalls Voice SDK delivers branded VoIP calls with CallKit integration and push-driven incoming calls. Before you start, register on SecuredCalls.com and obtain your config.dat file and client secret.
Topics
Jump to any part of the integration.
Add the package to Xcode with Swift Package Manager.
Declare microphone, notifications, contacts, location, and Face ID keys.
Background Modes, Push Notifications, App Groups, and Keychain Sharing.
Process incoming call pushes in a service extension.
Boot SecuredCalls with typography, settings, and biometric metadata.
Register the consumer, log in, manage tokens, and start a call.
Call back from Recents, Siri, and missed-call notifications.
Call-status and history delegates, history APIs, and SDK logs.
Prerequisites
Make sure your environment is ready.
- macOS with developer mode enabled
- Xcode 11.0 or above
- At least one physical iOS device running iOS 16 or later
- Swift 5.0 or later
- A SecuredCalls.com account with your
config.datfile and client secret
Section 1
Install the SDK
Add SecuredCalls to your project with Swift Package Manager. It takes about two minutes.
Add the package
In Xcode, choose File › Swift Packages › Add Package Dependency…, then paste the repository URL:
https://github.com/expertstack-studios/ios-securevoicecall-sdk
When prompted for a version, select Exact and enter 1.0.25, then click Next and Finish.
Section 2
Configure your project
Declare the privacy usage strings the SDK relies on, then enable the required capabilities.
Add privacy keys to Info.plist
The SDK needs microphone, notification, contacts, location, and Face ID access. Add a usage-description string for each so iOS can present the permission prompts.
Write user-facing reasons here — App Review rejects vague descriptions.
1<key>NSMicrophoneUsageDescription</key>2<string>Explain why microphone access is needed.</string>3 4<key>NSUserNotificationsUsageDescription</key>5<string>Explain why notifications are necessary.</string>6 7<key>NSContactsUsageDescription</key>8<string>Explain why access to contacts is needed.</string>9 10<key>NSLocationWhenInUseUsageDescription</key>11<string>Explain why access to location is needed.</string>12 13<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>14<string>Explain why access to location is needed.</string>15 16<key>NSFaceIDUsageDescription</key>17<string>Explain why biometric authentication is needed.</string>Enable capabilities
In your target’s Signing & Capabilities tab, click + and add each of the following:
- Background Modes → check Audio, AirPlay, and Picture in Picture, Voice over IP, Background fetch, and Remote notifications
- Push Notifications
- App Groups → configure the identifier
group.com.your.app
Enable Keychain Sharing (optional)
Enable this only if you want the SDK’s keychain items to be reachable from both your main app and its notification extension.
- In Signing & Capabilities, click + and add Keychain Sharing, then add a group, e.g.
com.your.app.shared. - Repeat for the Notification Service Extension target, using the same group name.
- Pass that group as
sharedKeychainGroupto bothSecuredCallsVoice.initialize(...)andSecuredCallsVoice.processNotificationAsync(...).
Pass the group without the team-identifier prefix (com.your.app.shared,
not ABCDE12345.com.your.app.shared). The SDK resolves and prepends the team
ID itself.
If you omit sharedKeychainGroup, the SDK does not set a keychain access group
and its items live in the app’s default group. No extra capability is required.
Section 3
Add a Notification Service Extension
This extension lets the SDK modify incoming call pushes before they reach the user.
Create the extension
Add a new Notification Service Extension target, link the SecuredCallsVoiceSDK framework, and add the App Groups capability with the same group.com.your.app identifier.
Then forward the push to the SDK from didReceive.
The App Group ID here must match the one passed to
SecuredCallsVoice.initialize(…) in the main app. If you use
Keychain Sharing, sharedKeychainGroup must match too.
1import SecuredCallsVoiceSDK2import UserNotifications3 4class NotificationService: UNNotificationServiceExtension {5 override func didReceive(6 _ request: UNNotificationRequest,7 withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void8 ) {9 Task {10 await SecuredCallsVoice.processNotificationAsync(11 request: request,12 // The incoming UNNotificationRequest received by the extension.13 14 appGroupID: "group.com.your.app",15 // MUST be the SAME App Group ID passed to16 // SecuredCallsVoice.initialize(...).17 18 sharedKeychainGroup: "com.your.app.shared",19 // Optional — pass only if you enabled Keychain Sharing.20 // MUST match the value passed to initialize(...), and the group21 // must be enabled on BOTH the app and the extension target.22 // Omit (or pass nil) to use the default keychain group.23 24 withContentHandler: contentHandler25 // Completion handler that returns the modified content to iOS.26 )27 }28 }29 30 override func serviceExtensionTimeWillExpire() {}31}Section 4
Wire up the AppDelegate
SwiftUI apps need an AppDelegate to receive push lifecycle callbacks.
Create an AppDelegate
Create a new Swift file named AppDelegate.swift and define a class conforming to UIApplicationDelegate.
1import UIKit2 3class AppDelegate: NSObject, UIApplicationDelegate {4 func application(5 _ application: UIApplication,6 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?7 ) -> Bool {8 // Perform any necessary setup here9 return true10 }11}Connect it to your App
In your @main App struct, use the @UIApplicationDelegateAdaptor property wrapper to attach the delegate.
1import SwiftUI2 3@main4struct AppName_SwiftUIApp: App {5 // Connect AppDelegate6 @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate7 8 var body: some Scene {9 WindowGroup {10 ContentPage()11 }12 }13}Section 5
Initialize the SDK
Boot SecuredCalls inside application(_:didFinishLaunchingWithOptions:).
Customize typography (optional)
Optionally build a Typography object to customize the fonts used across the SDK’s UI surfaces. Every field is optional; omit it entirely to use the SDK’s default system fonts.
Font customization is purely visual and does not affect SDK behavior. Any nil
font falls back to the default.
1// ALL typography fields are OPTIONAL. If Typography is not provided, or any2// font is nil, the SDK falls back to its default system fonts. Font3// customization is purely visual and does NOT affect SDK behavior.4var typography = Typography(5 displayName: UIFont(name: "AvenirNextLTPro-Demi", size: 36),6 // Large display titles, marquee-style text7 8 timer: UIFont(name: "AvenirNext-Medium", size: 32)?.withMonospacedDigits(),9 // Call duration timer — monospaced digits recommended for stable rendering10 11 callStatus: UIFont(name: "AvenirNext-Regular", size: 20),12 // Call status labels: "Incoming Call", "Connecting", "Dialing"13 14 poweredBy: UIFont(name: "AvenirNext-Medium", size: 18),15 // "Powered by" branding text16 17 callIntentTitle: UIFont(name: "AvenirNext-DemiBold", size: 16),18 // Call intent title text in the in-app call UI19 20 callIntentBody: UIFont(name: "AvenirNext-DemiBold", size: 24),21 // Call intent body / primary message text22 23 buttonTitle: UIFont(name: "AvenirNext-DemiBold", size: 14),24 // Button titles across the SDK UI25 26 callkitCallIntentTitle: UIFont(name: "AvenirNext-Regular", size: 8),27 // Call intent title on the iOS CallKit screen — smaller size recommended28 29 callkitCallIntentBody: UIFont(name: "AvenirNext-DemiBold", size: 16),30 // Call intent body text on the CallKit screen31 32 pipDisplayName: UIFont(name: "AvenirNext-DemiBold", size: 24),33 // Display name shown in Picture-in-Picture (PiP) mode34 35 keypadButtonTitle: UIFont(name: "AvenirNext-DemiBold", size: 24),36 // Dial pad / keypad button text37 38 sheetTitle: UIFont(name: "AvenirNext-DemiBold", size: 20),39 // Title of SDK-presented bottom sheets (e.g. the permission prompt)40 41 sheetBody: UIFont(name: "AvenirNext-Regular", size: 18),42 // Body text of SDK-presented bottom sheets43 44 sheetButtonTitle: UIFont(name: "AvenirNext-DemiBold", size: 18)45 // Button titles inside SDK-presented bottom sheets46)Call initialize()
Pass your client secret, config file name, settings, and App Group ID. Wrap the call in do/catch to surface configuration errors.
1func application(2 _ application: UIApplication,3 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?4) -> Bool {5 do {6 try SecuredCallsVoice.initialize(7 "xxxxxxxSECRETxxxxxxx",8 // SecuredCalls client secret provided by the SecuredCalls team.9 10 configFileName: "ConfigFileName",11 // Name of the configuration file (without extension).12 13 settings: ScSDKSettingsModel(14 handlePermission: true,15 // When true, the SDK checks and requests required permissions16 // via system popups if not already granted.17 18 showPipView: true,19 // Enables Picture-in-Picture (PiP) mode during an ongoing call.20 21 logLevel: .debug,22 // .error = 0, .warning = 1, .debug = 2, .information = 3,23 // .off = -1 (Default). Capitalised aliases (.Debug, .Off) work too.24 25 scCallKitIconName: "AppIcon-Mono",26 // Mono-color image name on the CallKit screen. MUST be monochrome.27 28 typography: typography,29 // Optional — if omitted, the SDK uses default typography.30 31 sessionReadyTimeoutSeconds: 3.0,32 // Optional — Default 3.0. Max seconds the SDK waits for its session33 // to become ready before calling back from a missed-call34 // notification. Increase on slow networks.35 36 showDataChannelConnectionStatus: false,37 // Optional — Default false. Shows a connection status indicator on38 // the call screen when the data channel is enabled.39 40 showDataChannelConnectionDetails: false41 // Optional — Default false. Makes the status indicator tappable,42 // expanding into a connection-details panel. No effect unless43 // showDataChannelConnectionStatus is also true.44 ),45 46 appGroupID: "group.com.your.app",47 // The SAME App Group ID must be used in BOTH the main app and the48 // notification extension.49 50 sharedKeychainGroup: "com.your.app.shared",51 // Optional — Default nil. Requires the "Keychain Sharing" capability on52 // the app AND the notification extension, using the same group name.53 // Must match the value passed to processNotificationAsync(...).54 55 biometricMetadata: biometricMetadata56 // Optional — Default nil. See "Biometric verification metadata".57 )58 } catch {59 print("Failed to initialize SecuredCallsVoice SDK: \(error.localizedDescription)")60 }61 return true62}try SecuredCallsVoice.initialize( _ secret: String, configFileName: String, settings: ScSDKSettingsModel, appGroupID: String, sharedKeychainGroup: String? = nil, biometricMetadata: [String: Any]? = nil ) throws
Your SecuredCalls client secret, provided by the SecuredCalls team.
Name of the configuration file (without extension) bundled with your app.
Runtime options — permission handling, PiP, log level, CallKit icon, optional typography, session-ready timeout, and data-channel status display.
App Group identifier used to share data between the main app and the Notification Service Extension.
Optional. Keychain Sharing group (without the team-ID prefix). Must match the
value passed to processNotificationAsync(...). Default nil.
Optional. Application context sent with a successful biometric verification.
Default nil.
ScSDKSettingsModel options
Fields on the settings model passed to initialize(...).
When true, the SDK checks and requests required permissions via system
popups if not already granted.
Enables Picture-in-Picture (PiP) mode so the user can keep using the app during an ongoing call.
.error = 0, .warning = 1, .debug = 2, .information = 3, .off = -1
(Default). Capitalised aliases (.Debug, .Off, …) still work.
Mono-color image name used on the CallKit screen. The image must be a monochrome asset.
Optional. Font customization. If omitted, the SDK uses default typography.
Optional. Default 3.0. Max seconds the SDK waits for its session to
become ready before calling back from a missed-call notification. Increase
on slow networks.
Optional. Default false. Shows a connection status indicator on the call
screen when the data channel is enabled for the call.
Optional. Default false. Makes the status indicator tappable, expanding
into a connection-details panel. No effect unless
showDataChannelConnectionStatus is also true.
Attach biometric metadata (optional)
biometricMetadata is an optional [String: Any] dictionary of your own application context. When a biometric verification succeeds during a call, the SDK sends this dictionary along with the verification result.
Supported value types are String, Int, Double, Bool, nil, and arrays
or dictionaries of those. Anything else (e.g. Date, Float, a custom type)
makes initialize(...) throw SecuredCallError.invalidBiometricMetadata.
Passing nil, or omitting the parameter, clears any previously stored metadata.
1let biometricMetadata: [String: Any] = [2 "CustomerId": userIdentifier,3 "accountId": accountId,4 "verificationContext": "ACCOUNT_ACCESS",5 "location": ["latitude": 37.7749, "longitude": -122.4194],6 "appInfo": [7 "deviceModel": "iPhone 16 Pro",8 "osVersion": "18.3.1",9 "appVersion": "4.2.1"10 ]11]Request permissions
After initializing, request notification, contact, location, microphone, and biometric access. With handlePermission: true the SDK presents the system prompts for you.
1UNUserNotificationCenter.current().delegate = self2 3try SecuredCallsVoice.initialize(4 "xxxxxxxSECRETxxxxxxx",5 configFileName: "ConfigFileName",6 settings: ScSDKSettingsModel(7 handlePermission: true,8 showPipView: true,9 logLevel: .Debug,10 scCallKitIconName: "AppIcon-Mono"11 ),12 appGroupID: "group.com.your.app"13)14 15// Request permissions asynchronously16Task {17 await SecuredCallsVoice.requestNotificationPermissionAsync()18 await SecuredCallsVoice.requestContactAccessAsync()19 await SecuredCallsVoice.requestLocationPermissionAsync()20 await SecuredCallsVoice.requestMicrophonePermissionAsync()21 await SecuredCallsVoice.requestBiometricPermissionAsync()22}Section 6
Register, log in, and call
Register the consumer once per install, log the user in, then place calls.
Register the consumer
Call registerConsumerAsync(customerId:) once per app installation, guarding against duplicate registration with UserDefaults.
For in-app-only calling the identifier can be any string. If you also use PSTN calling, it must be the user’s mobile number.
1let userIdentifier = "userIdentifier"2let key = "isConsumerRegistered"3 4// Skip if we've already registered on this install5guard !UserDefaults.standard.bool(forKey: key) else {6 print("Already registered")7 return8}9 10do {11 let result = try await SecuredCallsVoice.registerConsumerAsync(customerId: userIdentifier)12 switch result {13 case .success:14 logger.info("success")15 case .failure(let error):16 logger.info("failure: \(error.localizedDescription)")17 }18 UserDefaults.standard.set(true, forKey: key)19} catch {20 print("\(error.localizedDescription)")21}Log the user in
Register for VoIP pushes, then call loginAsync(identifier:) to establish the session.
1let userIdentifier = "userIdentifier"2 3func application(4 _ application: UIApplication,5 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil6) -> Bool {7 do {8 UNUserNotificationCenter.current().delegate = self9 registerForVoIPPushes()10 try SecuredCallsVoice.initialize(11 "xxxxxxxSECRETxxxxxxx",12 configFileName: "ConfigFileName",13 settings: ScSDKSettingsModel(14 handlePermission: true,15 showPipView: true,16 logLevel: .Debug,17 scCallKitIconName: "AppIcon-Mono"18 ),19 appGroupID: "group.com.your.app"20 )21 22 Task {23 await SecuredCallsVoice.requestNotificationPermissionAsync()24 await SecuredCallsVoice.requestContactAccessAsync()25 await SecuredCallsVoice.requestLocationPermissionAsync()26 await SecuredCallsVoice.requestMicrophonePermissionAsync()27 await SecuredCallsVoice.requestBiometricPermissionAsync()28 29 let loginResult = await SecuredCallsVoice.loginAsync(identifier: userIdentifier)30 switch loginResult {31 case .success:32 logger.info("success")33 case .failure(let error):34 logger.info("failure: \(error.localizedDescription)")35 }36 }37 } catch {38 print("\(error.localizedDescription)")39 }40 return true41}42 43private func registerForVoIPPushes() {44 let voipRegistry = PKPushRegistry(queue: nil)45 voipRegistry.delegate = self46 voipRegistry.desiredPushTypes = [.voIP]47}Hand the login task to the SDK (recommended)
When a user taps a missed-call notification while the app is not running, iOS cold-launches the app and may deliver the tap before your login finishes. Assign your login Task to SecuredCallsVoice.pendingLoginTask and the SDK awaits it before calling back, instead of guessing with a timeout.
If you do not set pendingLoginTask, the SDK falls back to waiting up to
sessionReadyTimeoutSeconds (default 3.0) for a login already in progress.
1let loginTask = Task<Result<Bool, Error>, Never> {2 let result = await SecuredCallsVoice.loginAsync(identifier: userIdentifier)3 switch result {4 case .success:5 logger.info("SecuredCallsVoice login status = success")6 case .failure(let error):7 logger.info("SecuredCallsVoice login status = failure: \(error.localizedDescription)")8 }9 return result10}11SecuredCallsVoice.pendingLoginTask = loginTaskRegister the APNs token
Forward the device token from didRegisterForRemoteNotificationsWithDeviceToken to the SDK as a hex string.
1func application(2 _ application: UIApplication,3 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data4) {5 let token = deviceToken.hexString6 Task {7 // false = sandbox, true = production. Set based on your deployment stage.8 let isProduction = false9 await SecuredCallsVoice.registerDeviceAsync(10 customerId: userIdentifier,11 token: token,12 isProduction: isProduction13 )14 }15}16 17extension Data {18 var hexString: String {19 map { String(format: "%02.2hhx", $0) }.joined()20 }21}Register the VoIP token
Implement PKPushRegistryDelegate and register the PushKit VoIP token from didUpdate whenever it changes.
1extension AppDelegate: PKPushRegistryDelegate {2 func pushRegistry(3 _ registry: PKPushRegistry,4 didUpdate pushCredentials: PKPushCredentials,5 for type: PKPushType6 ) {7 let isProduction = false8 if type == .voIP {9 Task {10 await SecuredCallsVoice.registerVoipTokenAsync(11 token: pushCredentials.token,12 isProduction: isProduction13 )14 }15 }16 }17}Report incoming VoIP pushes
From didReceiveIncomingPushWith, report the incoming VoIP push to the SDK, then call completion().
1func pushRegistry(2 _ registry: PKPushRegistry,3 didReceiveIncomingPushWith payload: PKPushPayload,4 for type: PKPushType,5 completion: @escaping () -> Void6) {7 if type == .voIP {8 SecuredCallsVoice.reportNewInComingCall(payload: payload)9 }10 completion()11}Place an outbound call
Start an in-app callback to your contact centre with startCallAsync(...). Only callType is required; the number, intent, and custom data are optional.
1Task {2 do {3 try await SecuredCallsVoice.startCallAsync(4 number: "61450000001",5 // Optional — if nil, the contact centre number from your config is used.6 7 callType: .inApp,8 // .inApp places the call through the SDK.9 // .pstn hands off to the native dialer.10 11 callIntent: "Card dispute follow-up",12 // Optional — Default "". Short reason surfaced on the call screen.13 14 customData: ["ticketId": "T-1029"]15 // Optional — Default [:]. Arbitrary key/value context for the call.16 )17 } catch {18 print("Error: \(error)")19 }20}try await SecuredCallsVoice.startCallAsync( number: String? = nil, callType: ScCallType, callIntent: String = "", customData: [String: Any] = [:] ) async throws
startCallAsync throws SecuredCallError.onGoingCall if a call is already
active, and SecuredCallError.invalidNumber if no number is available.
customData is also accepted by callBackFromCallHistory(...), which derives
the call intent itself.
Log the user out
End the session with logoutAsync(identifier:) when the user signs out.
1Task {2 if let userIdentifier = UserDefaults.standard.string(forKey: "userIdentifier") {3 do {4 try await SecuredCallsVoice.logoutAsync(identifier: userIdentifier)5 } catch {6 print("Logout failed: \(error)")7 }8 }9}Section 7
Handle history & missed-call callbacks
Call back when the user taps a Recents entry, uses Siri, or taps a missed-call notification.
Call back from phone history (SwiftUI)
When a user taps a call entry from the iOS Phone app’s Recents or starts a call with Siri, iOS delivers an NSUserActivity with a system call intent (INStartAudioCallIntent or INStartCallIntent). Extract the call identifier and forward it to callBackFromCallHistory(...).
1import SwiftUI2import Intents3import SecuredCallsVoiceSDK4 5@main6struct AppName_SwiftUIApp: App {7 8 @UIApplicationDelegateAdaptor(AppDelegate.self)9 var appDelegate10 11 var body: some Scene {12 WindowGroup {13 ContentView()14 .onContinueUserActivity(15 "INStartAudioCallIntent",16 perform: handleCallIntent17 )18 .onContinueUserActivity(19 "INStartCallIntent",20 perform: handleCallIntent21 )22 }23 }24 25 /// Receives system call intents and forwards the extracted call26 /// identifier to the SecuredCalls Voice SDK.27 private func handleCallIntent(_ userActivity: NSUserActivity) {28 guard let interaction = userActivity.interaction else { return }29 30 var callId: String?31 32 if let intent = interaction.intent as? INStartAudioCallIntent {33 callId = intent.contacts?.first?.personHandle?.value34 } else if let intent = interaction.intent as? INStartCallIntent {35 callId = intent.contacts?.first?.personHandle?.value36 }37 38 guard let callIdentifier = callId else { return }39 40 Task {41 do {42 try await SecuredCallsVoice.callBackFromCallHistory(43 callId: callIdentifier,44 callType: .inApp45 )46 } catch {47 print("SecuredCalls: failed to process call intent – \(error.localizedDescription)")48 }49 }50 }51}Handle missed-call notification taps
When a user misses an incoming call, the SDK schedules a local notification. Add userNotificationCenter(_:didReceive:withCompletionHandler:) to your UNUserNotificationCenterDelegate extension and forward the response to callBackFromMissedCallNotification.
callBackFromMissedCallNotification only acts on notifications whose
categoryIdentifier is MISSED_CALL and returns without doing anything
otherwise, so it is safe to forward every response to it.
1extension AppDelegate: UNUserNotificationCenterDelegate {2 3 func userNotificationCenter(4 _ center: UNUserNotificationCenter,5 didReceive response: UNNotificationResponse,6 withCompletionHandler completionHandler: @escaping () -> Void7 ) {8 Task {9 defer { completionHandler() }10 do {11 try await SecuredCallsVoice.callBackFromMissedCallNotification(response)12 } catch {13 print("callBackFromMissedCallNotification failed: \(error.localizedDescription)")14 }15 }16 }17}Refresh branding on foreground notifications
If you present notifications while the app is in the foreground, call SecuredCallsVoice.processNotification() so the SDK can notify your SecuredCallsVoiceDelegate that branding history changed.
1func userNotificationCenter(2 _ center: UNUserNotificationCenter,3 willPresent notification: UNNotification,4 withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void5) {6 completionHandler([.banner, .sound])7 SecuredCallsVoice.processNotification()8}Missed-call notification appearance
What the SDK-scheduled missed-call notification shows.
- Title: Call intent (if available), otherwise the app/brand name
- Body: 📞 Missed voice call
- Action: Tap to call back instantly
Section 8
Observe calls, history, and logs
Register delegates to react to call status and history changes, read and clear history, and pull SDK logs.
Observe call status
Conform to ICallStatusDelegate to be notified when a call starts, ends, or fails to connect. Register the delegate once, for example in your view model’s initializer.
Pass nil to setCallStatusDelegate(_:) to unregister. The delegate is held
weakly, so keep a strong reference to the object that conforms to it.
1import SecuredCallsVoiceSDK2 3final class CallViewModel: ICallStatusDelegate {4 5 init() {6 SecuredCallsVoice.setCallStatusDelegate(self)7 }8 9 func callStarted() {10 // A call is now connected.11 }12 13 func callEnded() {14 // The call finished.15 }16 17 func callFailed(reason: String) {18 // An outgoing call failed before connecting (e.g. poor network).19 }20}Read call & branding history
The SDK keeps two separate histories: call history (actual voice calls) and branding history (branded caller entries delivered by push). Both methods return [CallInfoModel], exposing callId, intent, note, contactNumber, businessName, brandImage, callType, callTime, isIncomingCall, callDuration, historyType, and branding colours.
1Task {2 switch await SecuredCallsVoice.getCallHistoryAsync() {3 case .success(let calls):4 print("\(calls.count) calls")5 case .failure(let error):6 print("Failed: \(error.localizedDescription)")7 }8 9 switch await SecuredCallsVoice.getBrandingHistoryAsync() {10 case .success(let branding):11 print("\(branding.count) branding entries")12 case .failure(let error):13 print("Failed: \(error.localizedDescription)")14 }15}Clear history
Clear calls only, branding only, or both.
1Task {2 await SecuredCallsVoice.clearCallHistoryAsync() // calls only3 await SecuredCallsVoice.clearBrandingHistoryAsync() // branding only4 await SecuredCallsVoice.clearAllCallHistoryAsync() // both5}React to history updates
Conform to SecuredCallsVoiceDelegate to refresh your UI when the SDK writes new history. didUpdateBrandingHistory() has a default empty implementation, so implement only what you need.
Like the call-status delegate, this delegate is held weakly.
1final class HistoryViewModel: SecuredCallsVoiceDelegate {2 3 init() {4 SecuredCallsVoice.setHistoryDelegate(self)5 }6 7 func didUpdateCallHistory() {8 // Reload call history.9 }10 11 func didUpdateBrandingHistory() {12 // Reload branding history.13 }14}Read & clear SDK logs
getLogs() returns the SDK’s recorded log lines, and clearLogs() removes them.
1Task {2 let lines: [String] = await SecuredCallsVoice.getLogs()3 await SecuredCallsVoice.clearLogs()4}Implementation time
A typical end-to-end integration takes about 40 minutes.
| Task | Estimated time |
|---|---|
| Add the SDK to your project | 2 min |
Configure Info.plist | 2 min |
| Enable capabilities | 2 min |
| Initialize the SDK | 2 min |
| Request permissions | 2 min |
| Register & log in | 5 min |
| APNs & VoIP token management | 5 min |
| Handle notifications & incoming calls | 5 min |
| Reporting incoming calls | 5 min |
| Callback from phone history | 5 min |
| Callback from missed-call notification | 5 min |
Total: ~40 minutes
Replace "xxxxxxxSECRETxxxxxxx" with your actual client secret throughout.
Callback from phone history via AppDelegate and SceneDelegate is documented
separately (in progress).