SDK iOS
# Foquz iOS SDK
**Версия:** 1.0.0 · **Платформа:** iOS 12.0+ · **Язык:** Swift · **Module:** `FoquzSDK`
## Введение
**Foquz iOS SDK** позволяет встраивать интерактивные виджеты обратной связи (NPS-опросы, анкеты, формы оценки) в iOS-приложение. SDK берёт на себя загрузку конфигурации с сервера, определение момента показа, отрисовку интерфейса виджетов и отправку собранных данных.
**Ключевые возможности:**
- Запуск виджетов по произвольным событиям приложения
- Настройка внешнего вида через объект темы `FoquzTheme`
- Передача атрибутов пользователя для точного таргетинга
- Уведомления о ходе кампании через протоколы делегатов
- Поддержка автоповорота при смене ориентации устройства
- Режим отладки с перехватом внутреннего лога SDK
---
## Системные требования
| | |
|---|---|
| Минимальная версия iOS | 12.0 |
| Swift | 5.7 и выше |
| Xcode | 14.0 и выше |
| Зависимости | Foundation, UIKit, SystemConfiguration, CoreTelephony |
---
## Установка
### CocoaPods
Добавьте в `Podfile`. Репозиторий specs и URL источника предоставляются командой Foquz при подключении к платформе.
```ruby
source 'https://github.com/CocoaPods/Specs.git'
source '<FOQUZ_PODSPEC_REPO_URL>'
platform :ios, '12.0'
use_frameworks!
target 'YourApp' do
pod 'FoquzSDK', '~> 1.0.0'
end
```
Затем выполните:
```bash
pod install
```
### Swift Package Manager
1. В Xcode откройте **File → Add Package Dependencies…**
2. Введите URL репозитория (предоставляется командой Foquz)
3. Выберите версию **1.0.0** и нажмите **Add Package**
### Ручная установка (.xcframework)
1. Перетащите `FoquzSDK.xcframework` в проект Xcode
2. В **Target → General → Frameworks, Libraries, and Embedded Content** установите **Embed & Sign**
3. Убедитесь, что в **Build Settings → Framework Search Paths** указан путь к фреймворку
---
## Инициализация
SDK инициализируется один раз при старте приложения — в `AppDelegate`, методе `application(_:didFinishLaunchingWithOptions:)`.
```swift
import UIKit
import FoquzSDK
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
Foquz.setup(
appID: "ваш-app-id",
settings: FoquzSettings()
)
return true
}
}
```
> **Правила инициализации:**
> - `Foquz.setup()` вызывается **один раз** при старте приложения.
> - Повторные вызовы `setup()` перезапускают SDK.
> - После инициализации единственный экземпляр SDK доступен через `Foquz.sdk`.
### Полная сигнатура setup()
```swift
static func setup(
appID: String,
settings: FoquzSettings,
campaignDelegate: FoquzCampaignDelegate? = nil,
logDelegate: FoquzLogDelegate? = nil
)
```
| Параметр | Тип | Обязательный | Описание |
|---|---|:---:|---|
| `appID` | `String` | ✅ | Идентификатор приложения из личного кабинета Foquz |
| `settings` | `FoquzSettings` | ✅ | Объект настроек SDK |
| `campaignDelegate` | `FoquzCampaignDelegate?` | | Делегат событий кампаний |
| `logDelegate` | `FoquzLogDelegate?` | | Делегат отладочного лога SDK |
---
## Запуск виджетов
Виджет запускается вызовом `startCampaign(eventName:)` из любого места приложения (ViewController, Service, и т.д.):
```swift
// Простой запуск по событию
Foquz.sdk.startCampaign(eventName: "order_completed")
// Запуск с атрибутами для таргетинга
let builder = FoquzAttributesBuilder()
let _ = builder.addValue("orderId", value: "ORD-8821")
let _ = builder.addValue("total", value: 4990.0)
let _ = builder.addValue("itemsCount", value: 3)
Foquz.sdk.startCampaign(eventName: "order_completed", attributes: builder.build())
```
Для принудительного скрытия показываемого виджета:
```swift
// Закрыть текущую активную кампанию (независимо от события)
Foquz.sdk.stopCampaign(eventForStop: nil)
// Закрыть кампанию, связанную с конкретным событием
Foquz.sdk.stopCampaign(eventForStop: "order_completed")
```
---
## Настройки SDK — FoquzSettings
`FoquzSettings` — класс настроек поведения SDK. Создайте экземпляр через `FoquzSettings()` (настройки по умолчанию) и настройте нужные поля перед передачей в `Foquz.setup()`.
```swift
let settings = FoquzSettings()
settings.debugEnabled = true
settings.globalDelayTimer = 3600
settings.closeOnSwipe = true
settings.slideInUiBlocked = true
settings.slideInUiBlackoutOpacity = 40
Foquz.setup(appID: appId, settings: settings)
```
> **Внимание:** Изменение сетевых параметров (`socketTimeout`, `retryTimeout`, `retryCount`) не рекомендуется без согласования с командой Foquz. Параметр `endpoint` принимает только строку специального формата — обратитесь к вашему менеджеру.
### Отладка
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
| `debugEnabled` | `Bool` | `false` | При `true` SDK выводит подробный лог в консоль и вызывает `FoquzLogDelegate.logDidReceive(message:)` |
### Задержка повторного показа
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
| `globalDelayTimer` | `Int` | `1800` | Минимальный интервал (сек.) между показами кампаний, у которых в настройках платформы не установлен флаг «показывать всегда» |
### Поведение формы
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
| `closeOnSwipe` | `Bool` | `true` | При `true` форма закрывается однократным свайпом вниз; при `false` — сворачивается, не закрываясь |
| `rotateToggle` | `Bool` | `true` | При `true` форма поддерживает автоповорот при смене ориентации устройства |
### Внешний вид — виджет slideIn
Виджет `slideIn` выезжает снизу экрана поверх контента.
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
| `slideInUiBlocked` | `Bool` | `false` | Блокировать взаимодействие с контентом за виджетом |
| `slideInUiBlackoutColor` | `String?` | `nil` | Цвет подложки в HEX-формате, например `"#000000"` |
| `slideInUiBlackoutOpacity` | `Int` | `0` | Непрозрачность подложки, 0–100 |
| `slideInUiBlackoutBlur` | `Int` | `0` | Размытие контента за виджетом, 0–100 |
### Внешний вид — виджет popup
Виджет `popup` отображается поверх всего экрана.
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
| `popupUiBlackoutColor` | `String?` | `nil` | Цвет подложки в HEX-формате |
| `popupUiBlackoutOpacity` | `Int` | `0` | Непрозрачность подложки, 0–100 |
| `popupUiBlackoutBlur` | `Int` | `0` | Размытие контента за виджетом, 0–100 |
### Сетевые параметры
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
| `socketTimeout` | `Double` | `25` | Таймаут соединения, сек. ⚠️ не менять |
| `retryTimeout` | `Double` | `300` | Пауза между попытками повтора, сек. ⚠️ не менять |
| `retryCount` | `Int` | `10` | Число повторных попыток ⚠️ не менять |
### Прочее
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
| `endpoint` | `String?` | `nil` | Адрес выделенного сервера (специальный формат, предоставляется менеджером) |
| `fieldsEventEnabled` | `Bool` | `true` | При `true` SDK уведомляет о заполнении каждого поля через `campaignDidAnswered()` |
---
## Оформление виджетов — FoquzTheme
`FoquzTheme` управляет внешним видом всех виджетов. Создайте экземпляр `FoquzTheme()` и назначьте его свойству `Foquz.sdk.theme`.
> **Важно:** все цвета темы задаются объектами `UIColor`. Используйте расширение `UIColor(rgba:defaultColor:)`, входящее в состав SDK, для создания цветов из HEX-строк формата `#RRGGBB` или `#RRGGBBAA`.
```swift
let theme = FoquzTheme()
theme.bgColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1)
theme.mainColor = UIColor("#1A73E8", defaultColor: .systemBlue)
theme.btnBorderRadius = 12
theme.formBorderRadius = 16
theme.fontH1 = UIFont(name: "Inter-Bold", size: 18) ?? .boldSystemFont(ofSize: 18)
Foquz.sdk.theme = theme
```
### Параметры темы
#### Основные цвета
| Свойство | Тип | Описание |
|---|---|---|
| `bgColor` | `UIColor` | Фон виджета |
| `mainColor` | `UIColor` | Акцентный цвет (прогресс-индикатор, выделения) |
| `iconColor` | `UIColor` | Цвет иконок (кнопка закрытия и т.д.) |
| `text01Color` | `UIColor` | Заголовки |
| `text02Color` | `UIColor` | Основной текст вопросов |
| `text03Color` | `UIColor` | Вспомогательный текст, плейсхолдеры |
| `errorColorPrimary` | `UIColor` | Цвет сообщений об ошибке |
| `errorColorSecondary` | `UIColor` | Фон блока ошибки |
#### Элементы управления
| Свойство | Тип | Описание |
|---|---|---|
| `btnBgColor` | `UIColor` | Фон кнопки |
| `btnBgColorActive` | `UIColor` | Фон кнопки при нажатии |
| `btnTextColor` | `UIColor` | Текст на кнопке |
| `inputBgColor` | `UIColor` | Фон поля ввода |
| `inputBorderColor` | `UIColor` | Рамка поля ввода |
| `controlBgColor` | `UIColor` | Фон чекбоксов и переключателей |
| `controlBgColorActive` | `UIColor` | Фон активного переключателя |
| `controlIconColor` | `UIColor` | Значок внутри активного элемента управления |
#### Геометрия
| Свойство | Тип | Описание |
|---|---|---|
| `formBorderRadius` | `CGFloat` | Радиус скругления углов карточки виджета |
| `btnBorderRadius` | `CGFloat` | Радиус скругления углов кнопок |
#### Типографика
| Свойство | Тип | Описание |
|---|---|---|
| `fontH1` | `UIFont` | Крупный заголовок |
| `fontH2` | `UIFont` | Заголовок второго уровня |
| `fontP1` | `UIFont` | Основной текст |
| `fontP2` | `UIFont` | Вспомогательный текст |
| `fontBtn` | `UIFont` | Надпись на кнопке |
| `fontCaption` | `UIFont` | Подписи, мелкие метки |
#### Дополнительные цвета
| Свойство | Тип | Описание |
|---|---|---|
| `iconStarColor` | `UIColor` | Цвет активных звёзд в оценках типа «звёзды» |
| `iconSmile1Color` | `UIColor` | Цвет первого смайла (худшая оценка) |
| `iconSmile2Color` | `UIColor` | Цвет второго смайла |
| `iconSmile3Color` | `UIColor` | Цвет третьего смайла |
| `iconSmile4Color` | `UIColor` | Цвет четвёртого смайла (лучшая оценка) |
| `skeletonBase` | `UIColor` | Базовый цвет скелетон-анимации загрузки |
| `skeletonShine` | `UIColor` | Цвет блика в скелетон-анимации |
| `bgDisabled` | `UIColor` | Фон отключённого элемента |
| `fgDisabled` | `UIColor` | Цвет текста/иконки отключённого элемента |
| `borderDisabled` | `UIColor` | Рамка отключённого элемента |
### Работа с цветами из HEX-строки
SDK включает расширение `UIColor`, позволяющее удобно создавать цвета из HEX:
```swift
// Из HEX #RRGGBB (с дефолтным цветом при ошибке)
theme.mainColor = UIColor("#E63946", defaultColor: .systemRed)
// Из HEX #RRGGBBAA (с прозрачностью)
theme.bgColor = UIColor("#FFFFFF80", defaultColor: .white)
// Получить HEX-строку из UIColor
let hex = theme.mainColor.hexString(false) // "#E63946"
```
---
## Атрибуты таргетинга — FoquzAttributesBuilder
`FoquzAttributesBuilder` формирует набор атрибутов для передачи при запуске кампании. На платформе Foquz атрибуты используются в правилах таргетинга.
Паттерн использования: создайте builder, добавьте нужные значения, вызовите `build()`.
```swift
let builder = FoquzAttributesBuilder()
let _ = builder.addValue("screen", value: "checkout")
let _ = builder.addValue("total", value: 4990.0)
let _ = builder.addValue("itemCount", value: 2)
let _ = builder.addValue("isPremium", value: true)
let _ = builder.addValue("signupDate", value: Date())
Foquz.sdk.startCampaign(
eventName: "checkout_viewed",
attributes: builder.build()
)
```
### Методы
| Метод | Тип значения | Возвращает |
|---|---|---|
| `addValue(_ name: String, value: String)` | Текст | `FoquzAttributesBuilder` |
| `addValue(_ name: String, value: Int)` | Целое число | `FoquzAttributesBuilder` |
| `addValue(_ name: String, value: Double)` | Дробное число | `FoquzAttributesBuilder` |
| `addValue(_ name: String, value: Bool)` | Булево значение | `FoquzAttributesBuilder` |
| `addValue(_ name: String, value: Date)` | Дата | `FoquzAttributesBuilder` |
| `build()` | — | `[FoquzAttribute]` |
> `build()` возвращает массив `[FoquzAttribute]`, который передаётся в `startCampaign(eventName:attributes:)`.
---
## Обработка событий
### FoquzCampaignDelegate
Реализуйте протокол `FoquzCampaignDelegate`, чтобы получать уведомления о состоянии кампаний.
```swift
protocol FoquzCampaignDelegate: AnyObject
```
Передайте делегат в `Foquz.setup()` или установите позже через `Foquz.sdk.campaignDelegate`.
#### Методы протокола
---
##### `campaignDidLoad(success:)`
```swift
func campaignDidLoad(success: Bool)
```
Вызывается после загрузки конфигурации кампаний с сервера.
| | Тип | Описание |
|---|---|---|
| `success` | `Bool` | `true` — конфигурация успешно загружена, `false` — произошла ошибка |
---
##### `campaignDidReceiveError(errorString:)`
```swift
func campaignDidReceiveError(errorString: String)
```
Вызывается при возникновении ошибки в процессе работы SDK.
| | Тип | Описание |
|---|---|---|
| `errorString` | `String` | Описание ошибки |
---
##### `campaignDidShow(campaignId:eventName:invocationId:)`
```swift
func campaignDidShow(
campaignId: Int,
eventName: String,
invocationId: String
)
```
Виджет показан пользователю.
| | Тип | Описание |
|---|---|---|
| `campaignId` | `Int` | Идентификатор кампании |
| `eventName` | `String` | Событие, инициировавшее показ |
| `invocationId` | `String` | Уникальный идентификатор конкретного вызова показа |
---
##### `campaignDidClose(campaignId:eventName:invocationId:)`
```swift
func campaignDidClose(
campaignId: Int,
eventName: String,
invocationId: String
)
```
Виджет закрыт (пользователь завершил или отклонил). Вызывается при любом закрытии.
| | Тип | Описание |
|---|---|---|
| `campaignId` | `Int` | Идентификатор кампании |
| `eventName` | `String` | Событие |
| `invocationId` | `String` | Идентификатор вызова |
---
##### `campaignDidTerminate(campaignId:eventName:terminatedPage:totalPages:invocationId:)`
```swift
func campaignDidTerminate(
campaignId: Int,
eventName: String,
terminatedPage: Int,
totalPages: Int,
invocationId: String
)
```
Пользователь закрыл виджет до завершения — не дошёл до последнего экрана.
| | Тип | Описание |
|---|---|---|
| `campaignId` | `Int` | Идентификатор кампании |
| `eventName` | `String` | Событие |
| `terminatedPage` | `Int` | Страница, на которой произошло закрытие (с 1) |
| `totalPages` | `Int` | Общее число страниц |
| `invocationId` | `String` | Идентификатор вызова |
---
##### `campaignDidSend(campaignId:invocationId:)`
```swift
func campaignDidSend(campaignId: Int, invocationId: String)
```
Данные кампании успешно отправлены на сервер.
| | Тип | Описание |
|---|---|---|
| `campaignId` | `Int` | Идентификатор кампании |
| `invocationId` | `String` | Идентификатор вызова |
---
##### `campaignDidAnswered(campaignId:answers:invocationId:)`
```swift
func campaignDidAnswered(
campaignId: Int,
answers: [String: Any],
invocationId: String
)
```
Пользователь прошёл кампанию до конца. Содержит все собранные ответы.
| | Тип | Описание |
|---|---|---|
| `campaignId` | `Int` | Идентификатор кампании |
| `answers` | `[String: Any]` | Словарь ответов: ключ — идентификатор блока, значение — ответ пользователя |
| `invocationId` | `String` | Идентификатор вызова |
---
##### `noCampaignToStart(eventName:invocationId:)`
```swift
func noCampaignToStart(eventName: String, invocationId: String)
```
По переданному событию не найдено ни одной доступной кампании.
| | Тип | Описание |
|---|---|---|
| `eventName` | `String` | Событие, для которого кампания не найдена |
| `invocationId` | `String` | Идентификатор вызова |
---
#### Пример реализации
```swift
extension MyViewController: FoquzCampaignDelegate {
func campaignDidLoad(success: Bool) {
print("Кампании загружены: \(success)")
}
func campaignDidReceiveError(errorString: String) {
print("Ошибка SDK: \(errorString)")
}
func campaignDidShow(campaignId: Int, eventName: String, invocationId: String) {
analytics.track("survey_shown", properties: [
"campaign_id": campaignId,
"event": eventName
])
}
func campaignDidClose(campaignId: Int, eventName: String, invocationId: String) {
print("Виджет закрыт: #\(campaignId)")
}
func campaignDidTerminate(
campaignId: Int, eventName: String,
terminatedPage: Int, totalPages: Int, invocationId: String
) {
analytics.track("survey_dismissed", properties: [
"campaign_id": campaignId,
"progress": "\(terminatedPage)/\(totalPages)"
])
}
func campaignDidSend(campaignId: Int, invocationId: String) {
print("Данные отправлены: #\(campaignId)")
}
func campaignDidAnswered(campaignId: Int, answers: [String: Any], invocationId: String) {
print("Ответы #\(campaignId): \(answers)")
}
func noCampaignToStart(eventName: String, invocationId: String) {
print("Нет кампании для события: \(eventName)")
}
}
```
---
### FoquzLogDelegate
```swift
protocol FoquzLogDelegate: AnyObject
```
Подключите делегат, чтобы перехватывать внутренний лог SDK. Работает при `FoquzSettings.debugEnabled = true`.
#### Метод протокола
##### `logDidReceive(message:)`
```swift
func logDidReceive(message: String)
```
| | Тип | Описание |
|---|---|---|
| `message` | `String` | Строка лога от SDK |
#### Пример
```swift
extension AppDelegate: FoquzLogDelegate {
func logDidReceive(message: String) {
// Перенаправление в свою систему логирования
Logger.debug("FoquzSDK: \(message)")
}
}
```
---
## Глобальные свойства — properties
Глобальные свойства идентифицируют пользователя и передаются на сервер Foquz вместе с каждым ответом на кампанию.
### Управление через словарь
```swift
// Задать сразу весь словарь
Foquz.sdk.properties = [
"clientId": "c_88291",
"email": "user@example.com",
"plan": "premium"
]
```
### Управление точечными методами
```swift
// Добавить / обновить одно свойство
Foquz.sdk.addGlobalProperty(key: "clientId", value: "c_88291")
Foquz.sdk.addGlobalProperty(key: "region", value: "Moscow")
// Удалить одно свойство
Foquz.sdk.removeGlobalProperty(key: "region")
// Получить все текущие свойства
let all = Foquz.sdk.getGlobalProperties()
// Очистить все свойства
Foquz.sdk.clearGlobalProperties()
```
---
## Справочник API — класс Foquz
`Foquz` — главный класс SDK. Предоставляет единственный экземпляр (синглтон) через свойство `sdk`.
### Статические члены
| | Описание |
|---|---|
| `Foquz.setup(appID:settings:campaignDelegate:logDelegate:)` | Инициализация SDK |
| `Foquz.sdk` | Синглтон SDK |
### Свойства экземпляра
| Свойство | Тип | Доступ | Описание |
|---|---|---|---|
| `version` | `String` | read-only | Текущая версия SDK |
| `settings` | `FoquzSettings` | read/write | Настройки SDK |
| `theme` | `FoquzTheme` | read/write | Тема оформления виджетов |
| `properties` | `[String: Any]` | read/write | Глобальные свойства пользователя |
| `campaignDelegate` | `FoquzCampaignDelegate?` | read/write | Делегат событий кампаний (слабая ссылка) |
| `logDelegate` | `FoquzLogDelegate?` | read/write | Делегат отладочного лога (слабая ссылка) |
### Методы экземпляра
| Метод | Описание |
|---|---|
| `startCampaign(eventName:attributes:)` | Запустить кампанию по событию (атрибуты опциональны) |
| `stopCampaign(eventForStop:)` | Закрыть кампанию; `eventForStop: nil` — закрывает любую активную |
| `addGlobalProperty(key:value:)` | Добавить / обновить глобальное свойство |
| `removeGlobalProperty(key:)` | Удалить глобальное свойство по ключу |
| `clearGlobalProperties()` | Очистить все глобальные свойства |
| `getGlobalProperties()` | Получить копию словаря всех глобальных свойств |
---
## Примеры
### Минимальная интеграция
```swift
// AppDelegate.swift
import FoquzSDK
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
Foquz.setup(appID: "ваш-app-id", settings: FoquzSettings())
return true
}
```
```swift
// ViewController.swift
class HomeViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Foquz.sdk.startCampaign(eventName: "home_screen")
}
}
```
*Foquz iOS SDK 1.0.0 · © 2026 Foquz*