SpamControl singleton.
Requires iOS 14+.
The integration uses up to three Call Directory Extensions, each backed by its own App Group. Configure only the ones you need, since each feature requires its extension:
Integration
Step 1: Add the SDK to your project
The SDK is distributed as a precompiledFeatureSpamControl.xcframework, provided by the Bemobi team. Drag it into your Xcode project, then under each target’s General > Frameworks, Libraries, and Embedded Content:
- Main app: add the framework as Embed & Sign.
- Each extension (next steps): add it as Do Not Embed, so it is linked but not duplicated in the extension bundle.
import FeatureSpamControl.
Step 2: Create the Call Directory Extensions
Add one target per extension you need. For each, go to File > New > Target, select Call Directory Extension under iOS, and name it (e.g.SpamControlDatasetDirectory, SpamControlUserDirectory, SpamControlIdentificationDirectory).
Step 3: Configure App Groups
Create one App Group per extension and share it between the main app and the matching extension. In each target’s Signing & Capabilities tab, add the App Groups capability:- Main app: all three groups
- Dataset extension:
group.your.app.dataset - User extension:
group.your.app.user - Identification extension:
group.your.app.identification
.entitlements file. Example for the main app:
.entitlements lists only its own group.
The App Group identifiers are yours to choose, but they must be identical everywhere they appear: the capability, the.entitlementsfile, the handler’sappGroup:argument (Step 5), and theExtensionBlockModel(Step 6).
Step 4: Link FeatureSpamControl to each extension
For every extension target, add FeatureSpamControl under General > Frameworks, Libraries, and Embedded Content and set it to Do Not Embed (see Step 1).
Step 5: Implement the extension handlers
In each extension’sCallDirectoryHandler.swift, delegate to the matching Wave handler.
The User and Identification handlers take a hintIdentifier: the label shown on the incoming call screen (e.g. "Blocked", "Spam").
Dataset extension (WaveCallDatasetDirectoryHandler):
WaveCallUserDirectoryHandler):
WaveCallIdentificationHandler):
Step 6: Add the contacts permission
AddNSContactsUsageDescription to your main app’s Info.plist. This is required only for manual blocking (User extension); the Dataset and Identification extensions work without contacts access.
Step 7: Configure the SDK
Configuration has two parts: declaring the extensions (once) and setting the API credentials (whenever they become available). Configure extensions. Call this once at startup (AppDelegate, @main, or an init service), passing one ExtensionBlockModel per extension. The appGroup and bundleIdentifier must match the App Groups from Step 3 and the extension targets’ bundle identifiers:
setup once you have the API key (provided by the Bemobi team) and the user’s phone number. It can be called after login; the phone number is not needed at launch. It must be called before any database operation, otherwise downloads fail with authentication errors.
setup can be called again if credentials change (e.g. a different user logs in); previous data is cleared automatically. Changing the environment between calls also clears all stored data.
Database operations require a physical device. downloadFullDatabase() throws on the iOS Simulator.
Example: enabling protection after login
Extensions are configured once at startup. Credentials and the database, however, are best set up right after the user logs in, when the phone number is available. A typical login handler looks like this:setup first, then downloadFullDatabase, then the two apply... calls that load the data into the extensions.
Step 8: Enable Call Blocking & Identification
The user must enable the extensions in iOS Settings. Build and run on a physical device, then go to Settings > Phone > Call Blocking & Identification and toggle on each of your extensions. You can open this screen directly withSpamControl.shared.openSettings().
Changes may take a few moments to apply. If an extension does not appear or does not work, verify that the App Groups, entitlements, and bundle identifiers match across the main app and the extension.
API reference
All methods are accessed throughSpamControl.shared and most are async/throws. Configuration helpers live on SpamControlConfiguration.shared. Only Brazilian numbers (55 + area code + number, e.g. 5511987654321) are processed.
Extension status
Report whether the user has enabled each extension under Settings > Phone > Call Blocking & Identification.isCallDatasetDirectoryEnabled() -> Bool: whether the Dataset (spam database) extension is enabled.isCallUserDirectoryEnabled() -> Bool: whether the User (manual blocking) extension is enabled.isCallIdentificationDirectoryEnabled() -> Bool: whether the Identification (caller ID) extension is enabled.areAllExtensionsEnabled() -> Bool: whether all three extensions are enabled.openSettings(): opens the system Call Blocking & Identification screen (iOS 13.4+).
Spam database
downloadFullDatabase(): downloads the latest spam database. Throttled to once per 24h (a no-op if called sooner) and throws on the Simulator.applySpamPhoneNumberBlocking(): loads the database into the Dataset extension so matching calls are blocked.applyIdentificationSpamPhoneNumberBlocking(): loads the database into the Identification extension so matching calls are labeled.totalPhoneNumbersAtDatabase() -> Int?: number of phone entries stored locally.
downloadFullDatabase() on each app launch to keep the database fresh; the 24h throttle prevents redundant downloads.
Categories
Categories let the user block whole groups of spam numbers at once.downloadSpamCategories(cacheValidityDuration:) -> [SpamCategory]: lists available categories. Uses the cache while valid (default 3600s / 1h).toggleCategoryBlockState(categoryId:) -> [SpamCategory]: flips one category’s block state and returns the updated list.blockSpamPhoneNumberByCategories(for: [NSNumber]): persists the selected category IDs and reloads the extension.
Manual blocking
Require the contacts permission and an enabled User extension. Blocking and unblocking return the affected entry IDs ([Int64]) and throw a typed BlockManuallyErrorType (see Error handling).
blockUserPhoneNumber(phoneNumber:) -> [Int64]: blocks a single number.blockUserPhoneNumbers(_: [String]) -> [Int64]: blocks several numbers at once.retrievedBlockedUserPhoneNumbers() -> [CXCallDirectoryPhoneNumber]: lists the numbers the user has blocked.unblockPhoneNumber(phoneNumber:) -> [Int64]: removes a number from the block list.
Trusted numbers
Trusted numbers are an allowlist: a trusted number is never blocked or labeled as spam, even if it appears in the spam database. Adding or removing one reloads the affected extensions. The throwing methods throwTrustedNumbersErrorType.
addTrustedNumber(_ phoneNumber: String): marks a number as trusted so it is no longer blocked.removeTrustedNumber(_ phoneNumber: String): removes a number from the trusted list, restoring its previous blocking state.getTrustedNumbers() -> [String]: lists the user’s trusted numbers.
Statistics
totalBlockedCalls() -> TotalBlockedCalls?: total number of calls blocked, fetched from the server.
Network
The SDK monitors connectivity to control downloads. Cellular downloads are disabled by default.isNetworkConnected: Bool: whether the device has any connection.isOnWiFi: Bool: whether the device is on Wi-Fi.downloadWithCellularDataEnabled: Bool: set totrueto allow downloads over cellular (defaultfalse).
Configuration helpers
OnSpamControlConfiguration.shared:
getBundleIdentifierExtension(with:) -> String: the bundle identifier configured for an extension type.getAppGroupIdentifier(for:) -> String: the App Group configured for an extension type.getSDKEnviroment() -> SDKEnviroment: the current environment (.productionor.stage).
Disabling the SDK
disabledSDK(): turns the SDK off. It removes the CallKit data from every extension, deletes the local spam database, and clears stored state (categories, last download time, etc.). To turn protection back on, callsetupanddownloadFullDatabaseagain.
Error handling
The manual blocking methods (blockUserPhoneNumber, blockUserPhoneNumbers, unblockPhoneNumber) throw a typed BlockManuallyErrorType you can switch on to give the user a precise message:
Error. Read error.localizedDescription for the cause, such as an authentication failure or an unavailable network:
SDK size
- 3 to 7 MB: core functionality (blocking and call management), no UI.
- 10 to 15 MB: full functionality including a SwiftUI user interface.
Migration to 2.0
TheWave class is deprecated. Use SpamControlConfiguration instead.