
Язык 🇺🇦 Русский (Украина)
Документация
Начало работы
Аутентификация
Использование
Add Comments to Your iOS App
Это официальная библиотека iOS для FastComments.
Встраивайте виджеты живых комментариев, чата и відгуків в ваше iOS‑приложение.
Репозиторий
Возможности 
- Древовидные комментарии с вложенными ответами и постраничной навигацией
- Социальная лента с созданием постов, реакциями и вложениями медиа
- Режим живого чата с автопрокруткой и разделителями по датам
- Обновления в реальном времени через WebSocket (новые комментарии, голоса, присутствие)
- Единый вход (Simple SSO для тестирования, Secure SSO для продакшна)
- Редактор с богатым форматированием: жирный, курсив, код и @упоминания
- Голосование с настраиваемыми стилями (стрелки вверх/вниз или сердечки)
- Действия модерации: пожаловаться, закрепить, закрыть, заблокировать
- Всесторонняя настройка тем с предустановками и полной кастомизацией
- Пользовательские кнопки на панели инструментов для комментариев и создания постов в ленте
- Загрузка изображений
- Поддержка региона ЕС
- Статус присутствия пользователей (индикаторы онлайн/оффлайн)
- Фильтрация ленты по тегам
- Поддержка локализации
Установка 
Добавьте FastCommentsUI в ваш проект с помощью Swift Package Manager.
В Xcode: Файл > Добавить зависимости пакета, затем введите URL репозитория.
Или добавьте его в ваш Package.swift:
dependencies: [
.package(url: "https://github.com/fastcomments/fastcomments-ios.git", from: "1.0.0")
]
Затем добавьте продукт в ваш target:
.target(
name: "YourApp",
dependencies: [
.product(name: "FastCommentsUI", package: "fastcomments-ios")
]
)
Импортируйте оба модуля там, где это необходимо:
import FastCommentsUI
import FastCommentsSwift
Быстрый старт 
Минимальная настройка для отображения виджета комментариев:
import SwiftUI
import FastCommentsUI
struct ContentView: View {
@StateObject private var sdk = FastCommentsSDK(
config: FastCommentsWidgetConfig(
tenantId: "demo",
urlId: "my-page-1",
url: "https://example.com/page-1",
pageTitle: "My Page"
)
)
var body: some View {
FastCommentsView(sdk: sdk)
.task {
try? await sdk.load()
}
}
}
Замените "demo" на ваш идентификатор арендатора FastComments. Параметр urlId идентифицирует страницу или ветку, где хранятся комментарии.
Аутентификация (SSO) 
FastComments підтримує три режими автентифікації:
- Анонімний -- без SSO-токена; користувачі отримують ідентичності, засновані на сесії
- Простий SSO -- токен на боці клієнта для демонстрацій і тестування (небезпечно)
- Захищений SSO -- серверно-підписаний токен для продакшену
Простий SSO
Корисно для демонстрацій і локального тестування. Будь-хто може видати себе за будь-якого користувача за допомогою Простого SSO, тому не використовуйте його в продакшені.
import FastCommentsSwift
let userData = SimpleSSOUserData(
username: "Jane Doe",
email: "jane@example.com",
avatar: "https://example.com/avatar.jpg"
)
let sso = FastCommentsSSO.createSimple(simpleSSOUserData: userData)
let token = try? sso.prepareToSend()
let config = FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "my-page-1",
sso: token
)
let sdk = FastCommentsSDK(config: config)
SimpleSSOUserData також підтримує необов'язкові поля:
id-- ідентифікатор користувача (по замовчуванню використовується email, якщо не вказано)displayName-- окреме ім'я для відображенняdisplayLabel-- користувацька мітка, показана поруч з ім'ям (наприклад, "VIP")websiteUrl-- посилання на імені користувачаlocale-- код локаліisProfileActivityPrivate-- приховати активність профілю (за замовчуванням true)
Захищений SSO
У продакшені ваш сервер генерує підписаний SSO-токен за допомогою вашого API секрета. iOS-додаток отримує цей токен з вашого сервера і передає його в конфігурацію.
На вашому бэкенді (з використанням FastComments Swift SDK або на будь-якій мові):
let userData = SecureSSOUserData(
id: "user-123",
email: "user@example.com",
username: "Display Name",
avatar: "https://example.com/avatar.jpg"
)
let sso = try FastCommentsSSO.createSecure(apiKey: "YOUR_API_KEY", secureSSOUserData: userData)
let token = try sso.prepareToSend()
// Верніть цей токен у ваш iOS-додаток через ваш API
У вашому iOS-додатку:
struct MyView: View {
@StateObject private var sdk = FastCommentsSDK(
config: FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "my-page-1"
)
)
@State private var isLoadingToken = true
var body: some View {
Group {
if isLoadingToken {
ProgressView("Loading...")
} else {
FastCommentsView(sdk: sdk)
}
}
.task {
// Отримайте токен з вашого бэкенду
let token = try? await fetchSSOTokenFromYourBackend()
// Створіть нову конфігурацію з токеном або встановіть його перед завантаженням
isLoadingToken = false
try? await sdk.load()
}
}
}
SecureSSOUserData підтримує додаткові поля:
optedInNotifications-- підписка на email-сповіщенняdisplayLabel-- користувацька міткаdisplayName-- ім'я для відображенняwebsiteUrl-- URL веб-сайтуgroupIds-- належність до групisAdmin-- права адміністратораisModerator-- права модератораisProfileActivityPrivate-- приватність активності профілю
Вложенные комментарии 
Базовое использование
struct CommentsPage: View {
@StateObject private var sdk = FastCommentsSDK(
config: FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "article-42",
url: "https://example.com/article/42",
pageTitle: "Article Title"
)
)
var body: some View {
FastCommentsView(sdk: sdk)
.task {
try? await sdk.load()
}
}
}
Стили голосования
Стиль голосования по умолчанию показывает стрелки вверх/вниз. Передайте ._1 для голосования в виде сердца:
FastCommentsView(sdk: sdk, voteStyle: ._1)
| Стиль | Внешний вид |
|---|---|
._0 |
Кнопки со стрелками вверх/вниз с общим счётом |
._1 |
Одна кнопка в виде сердца с показом количества |
Обработчики событий
Используйте колбэки в стиле модификаторов для обработки взаимодействий пользователя:
FastCommentsView(sdk: sdk)
.onCommentPosted { comment in
print("New comment: \(comment.commentHTML)")
}
.onReplyClick { renderableComment in
print("Replying to: \(renderableComment.comment.id)")
}
.onUserClick { context, userInfo, source in
// source — это .name или .avatar
print("Tapped \(userInfo.displayName)")
}
Применение темы
Передайте тему через окружение SwiftUI:
FastCommentsView(sdk: sdk)
.fastCommentsTheme(myTheme)
.task { try? await sdk.load() }
Или задайте её напрямую в SDK:
sdk.theme = FastCommentsTheme.modern
Направление сортировки
sdk.defaultSortDirection = .nf // Сначала новые (по умолчанию)
sdk.defaultSortDirection = .of // Сначала старые
sdk.defaultSortDirection = .mr // Наиболее релевантные
Онлайн-чат 
LiveChatView предоставляет чат в реальном времени с автопрокруткой, разделителями по дате и компактным макетом. Он автоматически настраивает SDK для сортировки от самых старых и немедленного отображения в режиме реального времени.
struct ChatView: View {
@StateObject private var sdk: FastCommentsSDK = {
let config = FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "chat-room-1",
sso: ssoToken // Рекомендуется SSO, чтобы у пользователей были имена
)
return FastCommentsSDK(config: config)
}()
var body: some View {
LiveChatView(sdk: sdk)
.onCommentPosted { comment in
print("Sent: \(comment.commentHTML)")
}
.task {
try? await sdk.load()
}
}
}
LiveChatView поддерживает следующие обратные вызовы:
.onCommentPosted-- срабатывает, когда пользователь отправляет сообщение.onCommentDeleted-- срабатывает, когда сообщение удаляется.onUserClick-- срабатывает, когда нажимают на имя пользователя или его аватар
Социальная лента 
Система ленты — отдельный SDK (FastCommentsFeedSDK) с собственным представлением.
Загрузка и отображение ленты
struct FeedPage: View {
@StateObject private var sdk: FastCommentsFeedSDK = {
let config = FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "my-feed",
sso: ssoToken
)
return FastCommentsFeedSDK(config: config)
}()
@State private var commentsPost: FeedPost?
var body: some View {
FastCommentsFeedView(sdk: sdk)
.onPostSelected { post in
commentsPost = post
}
.onCommentsRequested { post in
commentsPost = post
}
.onSharePost { post in
// Показать панель шаринга
}
.onUserClick { context, userInfo, source in
// Перейти к профилю пользователя
}
.onMediaClick { mediaItem, index in
// Открыть полноэкранный просмотр изображений
}
.task {
try? await sdk.load()
}
}
}
Представление ленты автоматически поддерживает жест «потянуть для обновления» и бесконечную прокрутку.
Создание постов
Используйте FeedPostCreateView для отображения формы создания поста:
@State private var showCreatePost = false
// В теле вашего представления:
.sheet(isPresented: $showCreatePost) {
FeedPostCreateView(
sdk: sdk,
onPostCreated: { post in
showCreatePost = false
Task { try? await sdk.refresh() }
},
onCancelled: {
showCreatePost = false
}
)
}
Реакции на посты
SDK обрабатывает реакции с оптимистическим обновлением:
try await sdk.reactPost(postId: post.id, reactionType: "l")
// Проверить состояние реакции
let hasLiked = sdk.hasUserReacted(postId: post.id, reactType: "l")
let likeCount = sdk.getLikeCount(postId: post.id)
Открытие комментариев к посту
Используйте CommentsSheet для отображения комментариев к посту ленты. Он создаёт экземпляр FastCommentsSDK внутри, используя конфигурацию SDK ленты:
.sheet(item: $commentsPost) { post in
CommentsSheet(post: post, feedSDK: sdk, onUserClick: { context, userInfo, source in
// Обработать клик по пользователю
})
}
Примечание: FeedPost должен соответствовать протоколу Identifiable для использования .sheet(item:). Добавьте это расширение:
extension FeedPost: @retroactive Identifiable {}
Фильтрация ленты по тегам
Реализуйте протокол TagSupplier, чтобы фильтровать посты ленты по тегам:
struct TeamTagSupplier: TagSupplier {
func getTags(currentUser: UserSessionInfo?) -> [String]? {
guard let user = currentUser else { return nil }
return ["team:\(user.id ?? "")", "public"]
}
}
sdk.tagSupplier = TeamTagSupplier()
Верните nil для нефильтрованной глобальной ленты.
Сохранение и восстановление состояния ленты
Сохраняйте состояние пагинации при событиях жизненного цикла представления:
let state = sdk.savePaginationState()
// Позже...
sdk.restorePaginationState(state)
Удаление постов
sdk.onPostDeleted = { postId in
print("Post \(postId) was deleted")
}
Настройка темы 
Предустановки темы
Доступны четыре встроенные предустановки:
// Системные настройки по умолчанию
sdk.theme = FastCommentsTheme.default
// Карточки с тенями и крупным скруглением углов
sdk.theme = FastCommentsTheme.modern
// Плоский, без теней, небольшой радиус скругления, без линий ветвления
sdk.theme = FastCommentsTheme.minimal
// Установить все цвета действий в один фирменный цвет
sdk.theme = FastCommentsTheme.allPrimary(.indigo)
Стиль отображения комментариев
var theme = FastCommentsTheme()
theme.commentStyle = .flat // Плоский список с разделителями (по умолчанию)
theme.commentStyle = .card // Скруглённые карточки с тенями
theme.commentStyle = .bubble // Стиль чат-пузырьков
Цвета
Все свойства цвета необязательны. Если значение не задано, используется разумный системный вариант по умолчанию.
var theme = FastCommentsTheme()
// Фирменные цвета
theme.primaryColor = .indigo
theme.primaryLightColor = .indigo.opacity(0.6)
theme.primaryDarkColor = Color(red: 0.2, green: 0.1, blue: 0.5)
// Фоны
theme.commentBackgroundColor = Color(.secondarySystemGroupedBackground)
theme.containerBackgroundColor = Color(.systemGroupedBackground)
// Кнопки действий
theme.actionButtonColor = .indigo
theme.replyButtonColor = .indigo
theme.toggleRepliesButtonColor = .indigo.opacity(0.8)
theme.loadMoreButtonTextColor = .indigo
// Голоса
theme.voteActiveColor = .red
theme.voteCountColor = .primary
theme.voteCountZeroColor = .secondary
theme.voteDividerColor = Color(.separator)
// Ссылки
theme.linkColor = .indigo
theme.linkColorPressed = .indigo.opacity(0.5)
// Диалоги
theme.dialogHeaderBackgroundColor = .indigo
theme.dialogHeaderTextColor = .white
// Поле ввода
theme.inputBarBackgroundColor = Color(.systemBackground)
theme.inputBarBorderColor = Color(.separator)
// Прочее
theme.onlineIndicatorColor = .green
theme.separatorColor = Color(.separator)
theme.badgeBackgroundColor = .gray.opacity(0.2)
theme.threadLineColor = .indigo.opacity(0.15)
Типографика
theme.commenterNameFont = .subheadline.weight(.bold)
theme.bodyFont = .body
theme.captionFont = .caption
theme.actionFont = .caption.weight(.medium)
Макет и отступы
theme.cornerRadius = .large // варианты: .none, .small, .medium, .large
theme.commentSpacing = 4 // Пункты между строками комментариев
theme.nestingIndent = 20 // Пункты отступа на уровень вложенности
theme.avatarSize = 36 // Диаметр аватара для корневых комментариев
theme.replyAvatarSize = 28 // Диаметр аватара для вложенных ответов
Визуальные эффекты
theme.showShadows = true // Тонкие тени на карточках
theme.showThreadLine = true // Вертикальная линия, соединяющая вложенные ответы
theme.animateVotes = true // Пружинная анимация при изменении голосов
Применение тем
Два подхода:
// Через окружение SwiftUI (рекомендуется для иерархии представлений)
FastCommentsView(sdk: sdk)
.fastCommentsTheme(theme)
// Напрямую в SDK
sdk.theme = theme
Пользовательские кнопки панели инструментов 
Comment Toolbar Buttons
Реализуйте протокол CustomToolbarButton, чтобы добавить кнопки в панель ввода комментария:
struct EmojiButton: CustomToolbarButton {
let id = "emoji"
let iconSystemName = "face.smiling" // Имя SF Symbol
let contentDescription = "Add Emoji"
let badgeText: String? = nil // Необязательный счётчик бейджа
func onClick(text: Binding<String>) {
text.wrappedValue += "\u{1F44D}"
}
// Необязательные переопределения (по умолчанию: true)
func isEnabled() -> Bool { true }
func isVisible() -> Bool { true }
}
Передайте пользовательские кнопки при создании представления:
FastCommentsView(
sdk: sdk,
customToolbarButtons: [EmojiButton(), CodeBlockButton()]
)
Или добавьте их глобально в SDK (применяется ко всем экземплярам):
sdk.addGlobalCustomToolbarButton(EmojiButton())
sdk.removeGlobalCustomToolbarButton(id: "emoji")
sdk.clearGlobalCustomToolbarButtons()
Feed Toolbar Buttons
Реализуйте FeedCustomToolbarButton для формы создания поста:
struct HashtagButton: FeedCustomToolbarButton {
let id = "hashtag"
let iconSystemName = "number"
let contentDescription = "Add Hashtag"
func onClick(content: Binding<String>) {
content.wrappedValue += "#"
}
}
Передайте их в представление создания:
FeedPostCreateView(
sdk: sdk,
customToolbarButtons: [HashtagButton()],
onPostCreated: { _ in },
onCancelled: { }
)
Или установите их глобально в SDK ленты:
sdk.globalFeedToolbarButtons = [HashtagButton()]
Модерация 
Действия, доступные всем пользователям
- Пожаловаться/Отменить жалобу -- сообщить о комментарии для проверки
try await sdk.flagComment(commentId: commentId)
try await sdk.unflagComment(commentId: commentId)
- Заблокировать/Разблокировать -- скрыть все комментарии от пользователя (локально, для каждого просматривающего)
try await sdk.blockUser(commentId: commentId)
try await sdk.unblockUser(commentId: commentId)
Действия только для администраторов
- Закрепить/Открепить -- поместить комментарий в начало ветки
try await sdk.pinComment(commentId: commentId)
try await sdk.unpinComment(commentId: commentId)
- Закрыть/Открыть -- запретить новые ответы на комментарий
try await sdk.lockComment(commentId: commentId)
try await sdk.unlockComment(commentId: commentId)
Все модерационные действия также доступны через контекстное меню комментария в интерфейсе. Действия администратора отображаются только тогда, когда текущий пользователь является администратором сайта (установлено через SSO флаг isAdmin или в настройках панели управления).
Обновления в реальном времени 
После вызова sdk.load() SDK автоматически подписывается на события WebSocket для настроенного urlId. Обрабатываются следующие события:
- Новые комментарии, правки и удаления
- Голоса (новые и удалённые)
- Изменения состояний закрепления, закрытия, пометки и блокировки
- Присутствие пользователей (вход/выход)
- Открытие/закрытие ветки
- Награждение значками
- Обновления конфигурации сервера
Управление отображением в реальном времени
По умолчанию новые комментарии от других пользователей появляются сразу:
sdk.showLiveRightAway = true // По умолчанию: показывать сразу
Установите это в false, чтобы буферизовать новые комментарии за кнопкой "N новых комментариев", позволяя пользователю выбрать, когда их показать:
sdk.showLiveRightAway = false
Присутствие пользователей
Индикаторы онлайн/оффлайн автоматически отображаются на аватарах пользователей, когда сервер включает отслеживание присутствия. Дополнительная настройка на клиенте не требуется.
Пагинация 
Размер страницы
// Комментарии: по умолчанию 30
sdk.pageSize = 50
// Лента: по умолчанию 10
feedSDK.pageSize = 20
Загрузка дополнительных комментариев
Интерфейс автоматически отображает элементы управления пагинацией. Вы также можете запускать пагинацию программно:
// Загрузить следующую страницу
try await sdk.loadMore()
// Загрузить все оставшиеся (отключено, если >2000 комментариев для производительности)
try await sdk.loadAll()
// Проверить состояние
sdk.hasMore // Существуют ли дополнительные страницы
sdk.shouldShowLoadAll()
sdk.getCountRemainingToShow()
Пагинация вложенных комментариев
Вложенные ответы загружаются лениво. Когда пользователь разворачивает ветку, загружаются первые 5 дочерних комментариев. Появляется элемент управления "загрузить ещё ответы", если есть дополнительные. Это обрабатывается автоматически интерфейсом.
Состояние и наблюдаемость 
Обе FastCommentsSDK и FastCommentsFeedSDK являются классами ObservableObject со свойствами, помеченными @Published. Вы можете наблюдать за ними в ваших SwiftUI представлениях для реактивного обновления интерфейса.
FastCommentsSDK Published Properties
| Property | Type | Description |
|---|---|---|
commentCountOnServer |
Int |
Общее количество комментариев на сервере |
newRootCommentCount |
Int |
Буферизованные новые комментарии (когда showLiveRightAway равно false) |
currentUser |
UserSessionInfo? |
Текущий аутентифицированный пользователь |
isSiteAdmin |
Bool |
Является ли текущий пользователь администратором сайта |
isClosed |
Bool |
Закрыта ли ветка комментариев |
hasBillingIssue |
Bool |
Есть ли проблема с оплатой |
isLoading |
Bool |
Выполняется ли сетевой запрос |
hasMore |
Bool |
Существуют ли дополнительные страницы комментариев |
blockingErrorMessage |
String? |
Ошибка, препятствующая работе интерфейса |
warningMessage |
String? |
Неблокирующее предупреждающее сообщение |
isDemo |
Bool |
Работает ли в демонстрационном режиме |
commentsVisible |
Bool |
Переключатель видимости комментариев |
toolbarEnabled |
Bool |
Показана ли панель форматирования |
FastCommentsFeedSDK Published Properties
| Property | Type | Description |
|---|---|---|
feedPosts |
[FeedPost] |
В данный момент загруженные посты ленты |
hasMore |
Bool |
Существуют ли дополнительные страницы |
currentUser |
UserSessionInfo? |
Текущий аутентифицированный пользователь |
blockingErrorMessage |
String? |
Блокирующее сообщение об ошибке |
isLoading |
Bool |
Выполняется ли сетевой запрос |
newPostsCount |
Int |
Количество новых постов с последней загрузки |
Дерево комментариев
К дереву комментариев можно получить доступ через sdk.commentsTree:
// Плоский список видимых узлов для отображения
sdk.commentsTree.visibleNodes
// Поиск комментария по ID
sdk.commentsTree.commentsById["comment-id"]
Регион ЕС 
Чтобы использовать европейский дата-центр, задайте поле region в вашей конфигурации:
let config = FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "my-page",
region: "eu"
)
Это направляет все API-запросы и WebSocket-подключения на eu.fastcomments.com.
Очистка 
Когда вы закончите работу с экземпляром SDK (например, когда представление закрывается), вызовите cleanup(), чтобы закрыть соединение WebSocket и отменить фоновые задачи:
sdk.cleanup()
Для представлений, управляемых @StateObject из SwiftUI, это обычно вызывается в .onDisappear или когда представление уничтожается.
Загрузка изображений 
Комментарии
let imageUrl = try await sdk.uploadImage(imageData: jpegData, filename: "photo.jpg")
Возвращает строку URL загруженного изображения.
Публикации ленты
let mediaItem = try await feedSDK.uploadImage(imageData: jpegData, filename: "photo.jpg")
// Загрузить несколько изображений параллельно
let mediaItems = try await feedSDK.uploadImages(images: [
(jpegData1, "photo1.jpg"),
(jpegData2, "photo2.jpg")
])
Упоминания пользователей 
Поиск пользователей для автодополнения @mention:
let results = try await sdk.searchUsers(query: "jan")
// Возвращает [UserSearchResult] с userId, username, avatar и т.д.
Встроенный CommentInputBar автоматически обрабатывает автодополнение @mention.
Редактирование и удаление комментариев 
Редактировать
try await sdk.editComment(commentId: commentId, newText: "Updated text")
Сервер повторно рендерит HTML. Локальный комментарий обновляется автоматически.
Удалить
try await sdk.deleteComment(commentId: commentId)
Удаление комментария также удаляет его потомков из локального дерева.
Обе операции доступны через контекстное меню комментария в интерфейсе, когда текущий пользователь является автором комментария (или администратором сайта).
Обработка ошибок 
SDK methods throw FastCommentsError, which conforms to LocalizedError:
do {
try await sdk.load()
} catch let error as FastCommentsError {
print(error.translatedError ?? error.reason ?? "Unknown error")
} catch {
print(error.localizedDescription)
}
FastCommentsError properties:
code-- код ошибки от APIreason-- описание ошибки на английском языкеtranslatedError-- локализованное сообщение об ошибке, предоставленное сервером
Blocking errors are also surfaced automatically via sdk.blockingErrorMessage, which the built-in views display to the user.
Локализация 
Передайте код локали в конфигурации, чтобы локализовать строки, предоставляемые сервером:
let config = FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "my-page",
locale: "fr_fr"
)
Строки интерфейса на стороне клиента используют локализацию, основанную на iOS-бандлах.
Пример приложения 
В репозитории содержится полнофункциональное примерное приложение в ExampleApp/ с демонстрациями:
- Тредовые комментарии с SSO и кастомными темами
- Социальная лента с созданием записей и фильтрацией по тегам
- Чат в реальном времени
- Простые и защищённые SSO-потоки
- Кастомные кнопки на панели инструментов (для комментариев и ленты)
Нужна помощь?
Если у вас возникнут какие-либо проблемы или вопросы по библиотеке iOS, пожалуйста:
Участие
Вклады приветствуются! Пожалуйста, посетите репозиторий на GitHub для ознакомления с правилами участия.