Skip to main content

Overview

SpamControl is a singleton class that provides functionalities for managing spam call directories, downloading databases, handling user-blocked phone numbers, scheduling background tasks, and monitoring network status. It interacts with CXCallDirectoryManager, Keychain, and NetworkMonitor to facilitate its operations. It’s available for iOS 14+

⚠️ Breaking Changes and Deprecations (Version 2.0)

Deprecated Classes and Methods

The following classes and methods have been deprecated in version 2.0 and will be removed in future versions. Please migrate to the new API:

Wave Class (Deprecated)

The entire Wave class and all its methods are deprecated. Use SpamControlConfiguration instead. Deprecated:
  • Wave.shared - Use SpamControlConfiguration.shared instead
  • Wave.shared.setup(apiKey:phoneNumber:) - Use SpamControlConfiguration.shared.setup(apiKey:phoneNumber:environment:) instead
  • Wave.shared.setIdentificationExtension(...) - Use SpamControlConfiguration.shared.configureExtensions(with:) instead
  • Wave.shared.setDatasetExtension(...) - Use SpamControlConfiguration.shared.configureExtensions(with:) instead
  • Wave.shared.setUserExtension(...) - Use SpamControlConfiguration.shared.configureExtensions(with:) instead
  • Wave.shared.getIdentificationExtension() - Use SpamControlConfiguration.shared.getBundleIdentifierExtension(with:) instead
  • Wave.shared.getDatasetExtension() - Use SpamControlConfiguration.shared.getBundleIdentifierExtension(with:) instead
  • Wave.shared.getUserExtension() - Use SpamControlConfiguration.shared.getBundleIdentifierExtension(with:) instead

Deprecated Methods in SpamControl

  • forceDownloadFullDatabase(for:) - This method is deprecated. Use downloadFullDatabase() instead, which now handles all download scenarios automatically.

Migration Guide

See the SDK Configuration section below for the new configuration approach using SpamControlConfiguration.

SDK Setup

The SpamControlConfiguration class is a singleton service that manages the framework’s setup and secure key storage. It provides configuration for Call Directory extensions and securely stores the API key using the keychain.

Setting Up App Groups for Data Sharing

To enable data sharing between your main app and the Call Directory Extensions, you must configure separate App Groups for each extension type. This allows these components to access shared containers for their specific data and resources.

Step 1: Enable App Groups in Xcode for the Main App

  1. Open your project in Xcode.
  2. Select your main app target in the project navigator.
  3. Go to the Signing & Capabilities tab.
  4. Click the + Capability button at the top of the screen.
  5. Search for and select App Groups.
  6. In the App Groups section, add the following group identifiers:
    • group.your.app.dataset (for the Dataset Extension)
    • group.your.app.user (for the User Extension)
    • group.your.app.identification (for the Identification Extension)

Step 2: Enable App Groups for Each Call Directory Extension

For the Dataset Extension:
  1. Select your Dataset Call Directory Extension target.
  2. Go to the Signing & Capabilities tab.
  3. Add App Groups capability.
  4. Check the box for group.your.app.dataset.
For the User Extension:
  1. Select your User Call Directory Extension target.
  2. Go to the Signing & Capabilities tab.
  3. Add App Groups capability.
  4. Check the box for group.your.app.user.
For the Identification Extension:
  1. Select your Identification Call Directory Extension target.
  2. Go to the Signing & Capabilities tab.
  3. Add App Groups capability.
  4. Check the box for group.your.app.identification.
Important: Each extension should only have access to its specific App Group to maintain proper data separation and security.

Step 3: Modify the App Group Entitlements

Ensure that the main app and Call Directory Extensions have the appropriate entitlements for using their respective App Groups.

Main App Entitlements:

In the main app’s .entitlements file, add the following keys for the app groups used by each extension:
<key>com.apple.security.application-groups</key>
<array>
    <string>group.your.app.dataset</string>
    <string>group.your.app.user</string>
    <string>group.your.app.identification</string>
</array>

Dataset Extension Entitlements:

In the Dataset Call Directory Extension’s .entitlements file:
<key>com.apple.security.application-groups</key>
<array>
    <string>group.your.app.dataset</string>
</array>

User Extension Entitlements:

In the User Call Directory Extension’s .entitlements file:
<key>com.apple.security.application-groups</key>
<array>
    <string>group.your.app.user</string>
</array>

Identification Extension Entitlements:

In the Identification Call Directory Extension’s .entitlements file:
<key>com.apple.security.application-groups</key>
<array>
    <string>group.your.app.identification</string>
</array>

Step 4: Configure the SDK

The SDK configuration is divided into two distinct steps that can be performed at different moments in your app’s lifecycle:

1. Extension Configuration (One-Time Setup)

Configure the Call Directory extensions using ExtensionBlockModel objects. This configuration needs to be done only once during your app’s initial setup and should be placed in your AppDelegate, @main struct, or any initialization service. ExtensionBlockModel Structure: Each extension is configured using an ExtensionBlockModel with the following properties:
  • appGroup (String): The App Group identifier for data sharing (e.g., “group.your.app.dataset”)
  • bundleIdentifier (String): The bundle identifier of the Call Directory extension target
  • extensionType (ExtensionBlockModel.ExtensionType): The type of extension:
    • .blockSpams - Blocks calls from known spam numbers using a dataset
    • .blockManual - Blocks calls from numbers manually added by the user
    • .callIdentifier - Identifies incoming calls with additional information
Example - Extensions Configuration:
import FeatureSpamControl

// This should be called in AppDelegate, @main struct, or any initialization code
func configureCallDirectoryExtensions() {
    SpamControlConfiguration.shared.configureExtensions(
        with: [
            // Configure identification extension
            ExtensionBlockModel(
                appGroup: "group.your.app.identification",
                bundleIdentifier: "com.yourcompany.app.CallIdentification",
                extensionType: .callIdentifier
            ),
            
            // Configure dataset extension  
            ExtensionBlockModel(
                appGroup: "group.your.app.dataset",
                bundleIdentifier: "com.yourcompany.app.DatasetDirectory",
                extensionType: .blockSpams
            ),
            
            // Configure user extension
            ExtensionBlockModel(
                appGroup: "group.your.app.user",
                bundleIdentifier: "com.yourcompany.app.UserDirectory",
                extensionType: .blockManual
            )
        ]
    )
}
Note: The configureExtensions(with:) method can be called multiple times, but typically should be called only once during app initialization.

2. API Credentials Setup (Required Before Database Operations)

The SpamControlConfiguration.shared.setup(apiKey:phoneNumber:environment:) method configures your API credentials and must be called BEFORE any database download operations.
Important: Unlike the extension configuration, the setup can be called at any point in your app - even after the user logs in. You don’t need to have the phoneNumber available at app launch.
setup(apiKey:phoneNumber:environment:)
  • apiKey (String): Your API key for authentication
  • phoneNumber (String): The user’s phone number in international format (e.g., “5511982619489”)
  • environment (SDKEnviroment): The target environment for API requests. Defaults to .production. Use .stage for development and testing purposes.
When to call:
  • ✅ After user login/authentication
  • ✅ When you have both API key and phone number available
  • ✅ Before calling downloadFullDatabase() or any database operations
  • ✅ Can be called multiple times if credentials change (e.g., different user login)
Why this matters: If you attempt to download the spam database without calling setup() first, the SDK will:
  • ❌ Fail to authenticate with the server
  • ❌ Unable to download the spam phone numbers database
  • ❌ Spam protection features won’t work properly
  • ❌ Network requests will return authentication errors

Common Implementation Patterns

Pattern 1: Setup After User Login
import FeatureSpamControl

class AuthenticationService {
    func handleSuccessfulLogin(apiKey: String, phoneNumber: String) {
        // Configure credentials when they become available
        SpamControlConfiguration.shared.setup(
            apiKey: apiKey,
            phoneNumber: phoneNumber,
            environment: .production
        )
        
        // Now you can safely download the database
        Task {
            do {
                try await SpamControl.shared.downloadFullDatabase()
                try await SpamControl.shared.applySpamPhoneNumberBlocking()
            } catch {
                print("Failed to setup spam protection: \(error)")
            }
        }
    }
}
Pattern 2: Setup in a ViewModel/Service
import FeatureSpamControl

class SpamProtectionViewModel: ObservableObject {
    @Published var isConfigured = false
    
    func initialize(apiKey: String, phoneNumber: String) async {
        // Setup can be called anywhere, not just in AppDelegate
        SpamControlConfiguration.shared.setup(
            apiKey: apiKey,
            phoneNumber: phoneNumber,
            environment: .production
        )
        
        do {
            try await SpamControl.shared.downloadFullDatabase()
            isConfigured = true
        } catch {
            print("Database download failed: \(error)")
        }
    }
}
Pattern 3: Check and Setup When Needed
import FeatureSpamControl

class SpamControlManager {
    private var isSetupComplete = false
    
    func ensureSetup(apiKey: String, phoneNumber: String) {
        guard !isSetupComplete else { return }
        
        SpamControlConfiguration.shared.setup(
            apiKey: apiKey,
            phoneNumber: phoneNumber,
            environment: .production
        )
        isSetupComplete = true
    }
    
    func downloadDatabase() async throws {
        // Ensure setup was called before attempting download
        guard isSetupComplete else {
            throw SpamControlError.notConfigured("Call SpamControlConfiguration.shared.setup() before downloading database")
        }
        
        try await SpamControl.shared.downloadFullDatabase()
    }
}

Configuration Checklist

  • Extension Configuration: Can be done once at app startup (AppDelegate, @main, etc.)
  • API Setup: Call SpamControlConfiguration.shared.setup() when you have API key and phone number (can be after login)
  • Before Download: Always ensure setup() was called before any downloadFullDatabase() operations
  • Flexible Timing: The phone number doesn’t need to be available at app launch - setup when ready
  • Environment Selection: Choose .production for production or .stage for development/testing

Important Notes

  • Extension Configuration: Should be done early in the app lifecycle for proper Call Directory integration
  • API Setup Flexibility: Can be called at any point when credentials become available (e.g., after user authentication)
  • Database Operations: All database download/update operations require setup() to be called first
  • Multiple Calls: You can call setup() multiple times if user credentials change (e.g., different user logs in). Previous credentials are automatically cleared.
  • Environment Changes: If the environment changes between setup calls, all stored data is automatically cleared
  • Thread Safety: The SpamControlConfiguration class uses a singleton pattern and is safe to call from the main thread
  • Error Handling: Extension configuration methods may throw errors if the configuration is invalid - handle them appropriately

Privacy Permissions Configuration

The SDK requires access to contacts to properly identify and block spam calls. You must add the appropriate privacy usage description to your app’s Info.plist file.

Required Info.plist Configuration

Add the following key to your main app’s Info.plist file to request contacts access permission:
<key>NSContactsUsageDescription</key>
<string>This app needs access to your contacts to identify known contacts and provide better spam call protection.</string>

Customizing the Permission Message

You should customize the usage description message to clearly explain to users why your app needs contacts access. This permission is specifically required for manual blocking functionality (User Extension) - the Dataset Extension and Identification Extension work independently without requiring contacts access. Here are some example messages you can use:
<!-- Example 1: Focus on manual blocking -->
<key>NSContactsUsageDescription</key>
<string>We need access to your contacts to allow you to manually block or unblock phone numbers and distinguish them from your known contacts.</string>

<!-- Example 2: Focus on user control -->
<key>NSContactsUsageDescription</key>
<string>Access to contacts is required for manual call blocking features, allowing you to add or remove numbers from your personal block list.</string>

<!-- Example 3: General explanation -->
<key>NSContactsUsageDescription</key>
<string>This permission enables manual phone number blocking functionality and helps distinguish between your contacts and numbers you want to block.</string>
Important: This permission is required specifically for the manual blocking functionality (User Extension). Without contacts access, users won’t be able to manually add or remove phone numbers from their personal block list. The Dataset Extension (spam database) and Identification Extension work independently and do not require contacts access.
Phone Number Format: The SDK only verifies and blocks phone numbers in the Brazilian format: 55 + Area Code + Phone Number (e.g., 5511987654321). Numbers in other formats will not be processed by the spam blocking system.

Extension Management

The SDK now supports three separate Call Directory extensions for comprehensive spam protection:
  1. Dataset Extension: Handles blocking numbers from the spam database
  2. User Extension: Handles numbers manually blocked by the user
  3. Identification Extension: Handles caller ID and spam identification

Retrieving Configured Extensions

// Get bundle identifiers for configured extensions
do {
    let datasetBundleId = try SpamControlConfiguration.shared.getBundleIdentifierExtension(with: .blockSpams)
    let userBundleId = try SpamControlConfiguration.shared.getBundleIdentifierExtension(with: .blockManual)
    let identificationBundleId = try SpamControlConfiguration.shared.getBundleIdentifierExtension(with: .callIdentifier)
    
    print("Dataset Extension: \(datasetBundleId)")
    print("User Extension: \(userBundleId)")
    print("Identification Extension: \(identificationBundleId)")
} catch {
    print("Error retrieving bundle identifiers: \(error)")
}

// Get App Group identifiers
do {
    let datasetAppGroup = try SpamControlConfiguration.shared.getAppGroupIdentifier(for: .blockSpams)
    let userAppGroup = try SpamControlConfiguration.shared.getAppGroupIdentifier(for: .blockManual)
    let identificationAppGroup = try SpamControlConfiguration.shared.getAppGroupIdentifier(for: .callIdentifier)
} catch {
    print("Error retrieving App Group identifiers: \(error)")
}

// Get current SDK environment
let environment = SpamControlConfiguration.shared.getSDKEnviroment()
print("Current environment: \(environment)") // .production or .stage

SDK’s usage

The SpamControl class provides functionality for managing call blocking, identifying spam calls, downloading datasets, and interacting with background tasks. It utilizes the Call Directory framework and other internal services to manage these tasks. Below is a breakdown of its key methods and properties.

Overview

  • Singleton: The class is implemented as a singleton, accessible via SpamControl.shared.
  • App Group Integration: The class leverages an app group for sharing data between the main app and its extension.
  • Network Monitoring: Monitors network connection and cellular data preferences for downloading datasets.
  • Call Directory Integration: Interacts with the Call Directory API to manage blocking and identifying phone numbers.
  • Background Task Management: Supports background tasks for updating call blocking data asynchronously.

Key Properties

downloadWithCellularDataEnabled

  • Type: Bool
  • Description: A flag that determines whether cellular data can be used for downloading datasets. Defaults to false.

isNetworkConnected

  • Type: Bool
  • Description: Returns whether the device is connected to the internet.

isOnWiFi

  • Type: Bool
  • Description: Returns whether the device is connected to a Wi-Fi network.

Key Methods

openSettings()

  • Availability: iOS 13.4 and later
  • Description: Opens the settings for the Call Directory extension.
  • Usage:
    try await SpamControl.shared.openSettings()

isCallDatasetDirectoryEnabled()

  • Returns: Bool
  • Description: Checks whether the call dataset directory is enabled.
  • Usage:
    let isEnabled = await SpamControl.shared.isCallDatasetDirectoryEnabled()

isCallUserDirectoryEnabled()

  • Returns: Bool
  • Description: Checks whether the user call directory is enabled.
  • Usage:
    let isEnabled = await SpamControl.shared.isCallUserDirectoryEnabled()

isCallIdentificationDirectoryEnabled()

  • Returns: Bool
  • Description: Checks whether the identification call directory is enabled.
  • Usage:
    let isEnabled = await SpamControl.shared.isCallIdentificationDirectoryEnabled()

areAllExtensionsEnabled()

  • Returns: Bool
  • Description: Checks whether all three call directory extensions are enabled.
  • Usage:
    let allEnabled = await SpamControl.shared.areAllExtensionsEnabled()

downloadFullDatabase()

  • Description: Downloads and prepares the spam phone number database for use. This method only downloads if 24 hours have passed since the last download.
  • Usage:
    try await SpamControl.shared.downloadFullDatabase()

forceDownloadFullDatabase(for phoneNumber: String)

  • Description: ⚠️ Deprecated in version 2.0. This method is deprecated. Use downloadFullDatabase() instead, which now handles all download scenarios automatically.
  • Parameters:
    • phoneNumber: The phone number used to fetch and register the database
  • Usage:
    // Deprecated - Use downloadFullDatabase() instead
    // try await SpamControl.shared.forceDownloadFullDatabase(for: "5511987654321")
    
    // Recommended approach
    try await SpamControl.shared.downloadFullDatabase()

applySpamPhoneNumberBlocking()

  • Description: Applies the current spam phone number list to the CallKit extension.
  • Usage:
    try await SpamControl.shared.applySpamPhoneNumberBlocking()

applyIdentificationSpamPhoneNumberBlocking()

  • Description: Applies the spam phone number identification settings to the CallKit extension.
  • Usage:
    try await SpamControl.shared.applyIdentificationSpamPhoneNumberBlocking()

downloadSpamCategories(cacheValidityDuration: TimeInterval = 3600)

  • Returns: [SpamCategory]
  • Description: Downloads all available spam categories associated with the authenticated phone number. This method retrieves categories from cache if available and not expired. Otherwise, it fetches fresh data from the remote service.
  • Parameters:
    • cacheValidityDuration: Maximum age of cached categories in seconds (default: 3600 seconds / 1 hour)
  • Usage:
    // Download with default cache duration (1 hour)
    let categories = try await SpamControl.shared.downloadSpamCategories()
    
    // Download with custom cache duration (2 hours)
    let categories = try await SpamControl.shared.downloadSpamCategories(cacheValidityDuration: 7200)

toggleCategoryBlockState(categoryId: Int)

  • Returns: [SpamCategory]
  • Description: Toggles the block state for a specific category and returns the complete list of categories with updated states.
  • Usage:
    let updatedCategories = try await SpamControl.shared.toggleCategoryBlockState(categoryId: 1)

blockSpamPhoneNumberByCategories(for categoriesIds: [NSNumber])

  • Description: Blocks spam phone numbers based on selected category identifiers. This method stores the selected category IDs in persistent storage and reloads the Call Directory extension.
  • Parameters:
    • categoriesIds: An array of category identifiers to block
  • Usage:
    try await SpamControl.shared.blockSpamPhoneNumberByCategories(for: [1, 2, 3])

totalBlockedCalls()

  • Returns: TotalBlockedCalls?
  • Description: Fetches the total number of blocked calls from the remote service.
  • Usage:
    let totalBlocked = try await SpamControl.shared.totalBlockedCalls()

totalPhoneNumbersAtDatabase()

  • Returns: Int?
  • Description: Returns the total count of phone entries stored in the database.
  • Usage:
    let totalNumbers = try await SpamControl.shared.totalPhoneNumbersAtDatabase()

blockUserPhoneNumbers(_ phoneNumbers: [String])

  • Description: Blocks a list of phone numbers (as String) and updates the call directory.
  • Parameters:
    • phoneNumbers: An array of phone numbers to block
  • Returns: [Int64]
  • Throws: An error if any phone number format is invalid, already blocked, or if the Call Directory is not enabled
  • Usage:
    let blockedList = try await SpamControl.shared.blockUserPhoneNumbers(["5511987654321", "5521987654321"])

blockUserPhoneNumber(phoneNumber: String)

  • Description: Blocks a user-provided phone number (as a String) and updates the call directory.
  • Parameters:
    • phoneNumber: The phone number to block
  • Returns: [Int64]
  • Throws: An error if the phone number format is invalid, already blocked, or if the Call Directory is not enabled
  • Usage:
    let blockedList = try await SpamControl.shared.blockUserPhoneNumber(phoneNumber: "5511987654321")

retrievedBlockedUserPhoneNumbers()

  • Returns: [CXCallDirectoryPhoneNumber]
  • Description: Retrieves the list of blocked user phone numbers.
  • Usage:
    let blockedNumbers = SpamControl.shared.retrievedBlockedUserPhoneNumbers()

unblockPhoneNumber(phoneNumber: String)

  • Description: Unblocks a specific phone number.
  • Parameters:
    • phoneNumber: The phone number to unblock
  • Throws: An error if the unblock operation fails
  • Usage:
    try await SpamControl.shared.unblockPhoneNumber(phoneNumber: "5511987654321")

Create a Call Directory Extension

This document provides a step-by-step guide on how to create and configure a Call Directory Extension for your app to block or identify incoming calls on iOS devices. The Call Directory Extension provides the functionality of blocking and identifying phone numbers without interfering with the user’s normal phone usage.

Step 1: Create New Call Directory Extensions

The SDK requires three separate Call Directory Extensions to provide comprehensive spam protection:
  1. Dataset Directory Extension: Handles blocking numbers from the spam database
  2. User Directory Extension: Handles numbers manually blocked by the user
  3. Identification Directory Extension: Handles caller ID and spam identification
Create the Dataset Directory Extension:
  1. Open your project in Xcode.
  2. Add a New Target:
    • Go to File > New > Target.
    • Under iOS, select Call Directory Extension and click Next.
    • Provide a name for your extension (e.g., SpamControlDatasetDirectory) and select the app you want to associate the extension with.
    • Click Finish.
Create the User Directory Extension:
  1. Add another New Target:
    • Go to File > New > Target.
    • Under iOS, select Call Directory Extension and click Next.
    • Provide a name for your extension (e.g., SpamControlUserDirectory) and select the app you want to associate the extension with.
    • Click Finish.
Create the Identification Directory Extension:
  1. Add a third New Target:
    • Go to File > New > Target.
    • Under iOS, select Call Directory Extension and click Next.
    • Provide a name for your extension (e.g., SpamControlIdentificationDirectory) and select the app you want to associate the extension with.
    • Click Finish.
This will create three new targets within your app with the necessary files for your Call Directory Extensions.

Step 2: Configure the Call Directory Extensions

Modify the Dataset Directory Extension CallDirectoryHandler.swift

In the CallDirectoryHandler.swift file, you will use the WaveCallDatasetDirectoryHandler to manage the phone numbers for blocking and identification. This approach ensures a streamlined integration with your existing FeatureSpamControl logic.

Understanding the hintIdentifier Parameter

The WaveCallUserDirectoryHandler and WaveCallIdentificationHandler accept an optional hintIdentifier parameter during initialization. This parameter defines the label text that appears on the user’s screen when an incoming call is identified. Purpose:
  • User Experience: Provides clear context to users about why a call is being flagged
  • Customization: Allows you to set a custom identification label in your preferred language
  • Caller ID Display: The hint appears directly on the incoming call screen
Common Examples:
  • For User Directory: “Blocked”, “Blocked by Me”, “Número Bloqueado”
  • For Identification: “Spam”, “Spam Likely”, “Possível Spam”, “Telemarketing”
Why it matters: When a call comes in, iOS displays the hint label below the phone number, helping users quickly identify unwanted calls. This improves the user experience by providing immediate visual feedback about the nature of the incoming call. For dataset directory extension (spam database blocking):
import CallDirectory
import FeatureSpamControl

class CallDirectoryHandler: CXCallDirectoryProvider {

    // MARK: - Properties
    
    private let directory = WaveCallDatasetDirectoryHandler(appGroup: "group.your.app.dataset")
    
    // MARK: - Instance Methods
    
    override func beginRequest(with context: CXCallDirectoryExtensionContext) {
        context.delegate = self

        directory.beginRequest(with: context) { result in
            switch result {
            case .success:
                context.completeRequest()
            case .failure(let error):
                context.cancelRequest(withError: error)
            }
        }
    }
}

// MARK: - CXCallDirectoryExtensionContextDelegate

extension CallDirectoryHandler: CXCallDirectoryExtensionContextDelegate {

    func requestFailed(
        for extensionContext: CXCallDirectoryExtensionContext,
        withError error: Error
    ) {
        directory.requestFailed(for: extensionContext, withError: error)
    }
}
For user directory extension (user-blocked numbers):
import Foundation
import CallKit
import FeatureSpamControl

class CallDirectoryHandler: CXCallDirectoryProvider {

    // MARK: - Properties

    private let directory = WaveCallUserDirectoryHandler(
        appGroup: "group.your.app.user",
        hintIdentifier: "Blocked"
    )

    // MARK: - Instance Methods

    override func beginRequest(with context: CXCallDirectoryExtensionContext) {
        context.delegate = self

        do {
            try directory.beginRequest(with: context)
        } catch {
            let error = NSError(domain: "CallDirectoryHandler", code: 1, userInfo: nil)
            context.cancelRequest(withError: error)
            return
        }
        context.completeRequest()
    }
}

// MARK: - CXCallDirectoryExtensionContextDelegate

extension CallDirectoryHandler: CXCallDirectoryExtensionContextDelegate {

    func requestFailed(
        for extensionContext: CXCallDirectoryExtensionContext,
        withError error: Error
    ) {
        directory.requestFailed(for: extensionContext, withError: error)
    }
}
For identification directory extension (caller ID and spam identification):
import Foundation
import CallKit
import FeatureSpamControl

class CallDirectoryHandler: CXCallDirectoryProvider {

    // MARK: - Properties

    private let directory = WaveCallIdentificationHandler(
        appGroup: "group.your.app.identification",
        hintIdentifier: "Spam"
    )

    // MARK: - CXCallDirectoryProvider

    override func beginRequest(with context: CXCallDirectoryExtensionContext) {
        context.delegate = self
        
        directory.beginRequest(with: context) { result in
            switch result {
            case .success:
                context.completeRequest()
            case .failure(let error):
                context.cancelRequest(withError: error)
            }
        }
    }
}

// MARK: - CXCallDirectoryExtensionContextDelegate

extension CallDirectoryHandler: CXCallDirectoryExtensionContextDelegate {

    func requestFailed(
        for extensionContext: CXCallDirectoryExtensionContext,
        withError error: Error
    ) {
        directory.requestFailed(for: extensionContext, withError: error)
    }
}

Adding FeatureSpamControl as a Dependency to the Call Directory Extension

To integrate the FeatureSpamControl framework into your Call Directory Extension, follow these steps. This will allow the extension to leverage the features and functionality provided by FeatureSpamControl, without embedding the framework directly into the extension.

Step 1: Add FeatureSpamControl as a Dependency in Your Call Directory Extension

  1. Open your Xcode project.
  2. Select the Call Directory Extension target from the project navigator.
  3. Go to the General tab.
  4. In the Frameworks, Libraries, and Embedded Content section, click the + button.
  5. Search for FeatureSpamControl and add it to the list of dependencies.

Step 2: Set FeatureSpamControl to Not Be Embedded

To prevent FeatureSpamControl from being embedded into the extension’s bundle (which is the default behavior when adding a framework), you need to set it to Do Not Embed.
  1. After adding FeatureSpamControl as a dependency, locate the Embedded Content column in the Frameworks, Libraries, and Embedded Content section.
  2. Change the setting for FeatureSpamControl to Do Not Embed. This ensures that the framework is linked, but not included in the extension bundle, as it will be shared through the App Group.
Note: This is important to ensure that the framework is not duplicated inside the extension’s bundle, reducing app size and avoiding conflicts.

Step 3: Verify FeatureSpamControl is Linked Correctly

Ensure that the framework is properly linked and accessible by the extension:
  1. Go to the Build Phases tab of your Call Directory Extension target.
  2. Under Link Binary With Libraries, confirm that FeatureSpamControl is listed. This confirms that the framework is correctly linked and accessible to the extension during runtime.

Step 4: Use FeatureSpamControl in the Call Directory Extension

Once the framework is added as a dependency (without embedding), you can begin using it within the extension using the appropriate handlers as shown in the examples above.

User Phone Number Management

The SDK provides comprehensive functionality for managing user-blocked phone numbers separately from the spam database.

Blocking User Phone Numbers

// Block a list of phone numbers manually added by the user
let phoneNumbers: [String] = ["1234567890", "9876543210"]
try await SpamControl.shared.blockUserPhoneNumbers(phoneNumbers)

// Block a single phone number
let blockedList = try await SpamControl.shared.blockUserPhoneNumber(phoneNumber: "1234567890")

Retrieving Blocked User Phone Numbers

// Get all phone numbers blocked by the user
let blockedNumbers = SpamControl.shared.retrievedBlockedUserPhoneNumbers()

Unblocking a Specific Phone Number

// Unblock a specific phone number
try await SpamControl.shared.unblockPhoneNumber(phoneNumber: "1234567890")

Spam Categories Management

The SDK now supports category-based spam filtering, allowing users to select which types of spam calls they want to block.

Downloading Available Categories

// Download all available spam categories
let categories = try await SpamControl.shared.downloadSpamCategories()

// Download with custom cache duration (default: 1 hour)
let categories = try await SpamControl.shared.downloadSpamCategories(cacheValidityDuration: 7200) // 2 hours

Toggling Category Block State

// Toggle the block state for a specific category
let updatedCategories = try await SpamControl.shared.toggleCategoryBlockState(categoryId: 1)

Blocking by Categories

// Block spam numbers based on selected category IDs
let categoryIDs: [NSNumber] = [1, 2, 3] // Example category IDs
try await SpamControl.shared.blockSpamPhoneNumberByCategories(for: categoryIDs)

Statistics and Analytics

Total Blocked Calls

// Get statistics on total blocked calls
let totalBlocked = try await SpamControl.shared.totalBlockedCalls()
print("Total blocked calls: \(totalBlocked?.value ?? 0)")

Database Statistics

// Get total count of phone numbers in the spam database
let totalNumbers = try await SpamControl.shared.totalPhoneNumbersAtDatabase()
print("Total phone numbers in database: \(totalNumbers ?? 0)")

Network Management

The SDK includes network monitoring capabilities to optimize data usage:

Key Properties

  • isNetworkConnected: Returns whether the device is connected to the internet
  • isOnWiFi: Returns whether the device is connected to a Wi-Fi network
  • downloadWithCellularDataEnabled: A flag that determines whether cellular data can be used for downloading datasets (defaults to false)

Usage Example

// Check network status before downloading
if SpamControl.shared.isNetworkConnected {
    if SpamControl.shared.isOnWiFi || SpamControl.shared.downloadWithCellularDataEnabled {
        // Proceed with network operations
        try await SpamControl.shared.downloadFullDatabase()
    }
}

// Enable cellular data downloads if needed
SpamControl.shared.downloadWithCellularDataEnabled = true

Enabling Call Blocking & Identification in the Phone App

After building and running your app and Call Directory Extension, the next step is to enable the Call Blocking & Identification feature in the Phone app on the device. This allows your extension to block or identify phone calls based on the numbers you’ve provided in your code.

Step 1: Install and Run the App

  1. Build and run the app and extension on a physical iOS device.
  2. Ensure that the app and extension are properly installed on the device.
  3. The app should be visible on the home screen, and the Call Directory Extension should be available for configuration in the Settings app.

Step 2: Open the Settings App

  1. On the device, open the Settings app.
  2. Scroll down and tap Phone.

Step 3: Enable the Call Blocking & Identification Toggle

  1. In the Phone settings, find and tap on Call Blocking & Identification.
  2. You will see a list of all the Call Directory Extensions installed on your device.
  3. Find your app’s Call Directory Extension in the list (it will display the name you set for the extension).
  4. Enable the toggle next to your extension to turn on Call Blocking & Identification.
    • This will allow your extension to start identifying and blocking calls based on the phone numbers you’ve configured in your extension.

Step 4: Verify Call Blocking and Identification

Once the toggle is enabled, you can verify that the feature works as expected:
  1. Test Blocking: Try receiving a call from a number you’ve added to the blocked numbers list in your extension’s code. The call should be blocked.
  2. Test Identification: Try receiving a call from a number you’ve added to the identified numbers list in your extension’s code. The caller ID should be updated to reflect the label you’ve set for the number (e.g., “Spam Caller”).
Note: Changes in the Call Directory Extension may take a few moments to take effect. If you don’t see immediate results, try toggling the switch off and back on.

Troubleshooting

  • Call Directory Extensions Not Appearing: If your extensions do not appear in the Call Blocking & Identification list, ensure that both extensions are properly installed and that you’ve correctly followed the setup steps for each one.
  • Extensions Not Working: If the extensions are enabled but calls are not being blocked or identified, check that:
    • Numbers are correctly added to the respective extensions (spam database vs. user-blocked numbers)
    • Both extensions have the correct entitlements and permissions
    • App Groups are properly configured for data sharing
    • The extensions are using the correct bundle identifiers and app group identifiers
  • Partial Functionality: If only one type of blocking works (either spam database or user-blocked), verify that both extensions are enabled in Settings and have proper app group access.

SDK estimated size

The size variations depend on the specific features and resources included in the SDK, such as call management logic, UI components, and any other integrations.
  • 3 MB to 7 MB: SDK with basic functionality, including an entry point and methods for blocking and managing calls. This version does not include a user interface (UI).
  • 10 MB to 15 MB: SDK with full functionality, including an entry point, methods for blocking and managing calls, and a user interface (UI) built using SwiftUI.