FastComments.com

Add Comments to Your iOS App

Это официальная библиотека iOS для FastComments.

Встраивайте виджеты живых комментариев, чата и відгуків в ваше iOS‑приложение.

Репозиторий

Просмотреть на GitHub


Возможности Internal Link

  • Древовидные комментарии с вложенными ответами и постраничной навигацией
  • Социальная лента с созданием постов, реакциями и вложениями медиа
  • Режим живого чата с автопрокруткой и разделителями по датам
  • Обновления в реальном времени через WebSocket (новые комментарии, голоса, присутствие)
  • Единый вход (Simple SSO для тестирования, Secure SSO для продакшна)
  • Редактор с богатым форматированием: жирный, курсив, код и @упоминания
  • Голосование с настраиваемыми стилями (стрелки вверх/вниз или сердечки)
  • Действия модерации: пожаловаться, закрепить, закрыть, заблокировать
  • Всесторонняя настройка тем с предустановками и полной кастомизацией
  • Пользовательские кнопки на панели инструментов для комментариев и создания постов в ленте
  • Загрузка изображений
  • Поддержка региона ЕС
  • Статус присутствия пользователей (индикаторы онлайн/оффлайн)
  • Фильтрация ленты по тегам
  • Поддержка локализации

Требования Internal Link

  • iOS 16+ или macOS 14+
  • Swift 5.9+
  • SwiftUI

Установка Internal Link


Добавьте 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

Быстрый старт Internal Link


Минимальная настройка для отображения виджета комментариев:

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) Internal Link

FastComments підтримує три режими автентифікації:

  1. Анонімний -- без SSO-токена; користувачі отримують ідентичності, засновані на сесії
  2. Простий SSO -- токен на боці клієнта для демонстрацій і тестування (небезпечно)
  3. Захищений 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 -- приватність активності профілю


Вложенные комментарии Internal Link

Базовое использование

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  // Наиболее релевантные


Онлайн-чат Internal Link

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 -- срабатывает, когда нажимают на имя пользователя или его аватар


Социальная лента Internal Link


Система ленты — отдельный 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")
}


Настройка темы Internal Link

Предустановки темы

Доступны четыре встроенные предустановки:

// Системные настройки по умолчанию
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


Пользовательские кнопки панели инструментов Internal Link

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()]


Модерация Internal Link

Действия, доступные всем пользователям

  • Пожаловаться/Отменить жалобу -- сообщить о комментарии для проверки
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 или в настройках панели управления).



Обновления в реальном времени Internal Link

После вызова sdk.load() SDK автоматически подписывается на события WebSocket для настроенного urlId. Обрабатываются следующие события:

  • Новые комментарии, правки и удаления
  • Голоса (новые и удалённые)
  • Изменения состояний закрепления, закрытия, пометки и блокировки
  • Присутствие пользователей (вход/выход)
  • Открытие/закрытие ветки
  • Награждение значками
  • Обновления конфигурации сервера

Управление отображением в реальном времени

По умолчанию новые комментарии от других пользователей появляются сразу:

sdk.showLiveRightAway = true   // По умолчанию: показывать сразу

Установите это в false, чтобы буферизовать новые комментарии за кнопкой "N новых комментариев", позволяя пользователю выбрать, когда их показать:

sdk.showLiveRightAway = false

Присутствие пользователей

Индикаторы онлайн/оффлайн автоматически отображаются на аватарах пользователей, когда сервер включает отслеживание присутствия. Дополнительная настройка на клиенте не требуется.



Пагинация Internal Link

Размер страницы

// Комментарии: по умолчанию 30
sdk.pageSize = 50

// Лента: по умолчанию 10
feedSDK.pageSize = 20

Загрузка дополнительных комментариев

Интерфейс автоматически отображает элементы управления пагинацией. Вы также можете запускать пагинацию программно:

// Загрузить следующую страницу
try await sdk.loadMore()

// Загрузить все оставшиеся (отключено, если >2000 комментариев для производительности)
try await sdk.loadAll()

// Проверить состояние
sdk.hasMore            // Существуют ли дополнительные страницы
sdk.shouldShowLoadAll()
sdk.getCountRemainingToShow()

Пагинация вложенных комментариев

Вложенные ответы загружаются лениво. Когда пользователь разворачивает ветку, загружаются первые 5 дочерних комментариев. Появляется элемент управления "загрузить ещё ответы", если есть дополнительные. Это обрабатывается автоматически интерфейсом.



Состояние и наблюдаемость Internal Link

Обе 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"]


Регион ЕС Internal Link


Чтобы использовать европейский дата-центр, задайте поле region в вашей конфигурации:

let config = FastCommentsWidgetConfig(
    tenantId: "YOUR_TENANT_ID",
    urlId: "my-page",
    region: "eu"
)

Это направляет все API-запросы и WebSocket-подключения на eu.fastcomments.com.



Очистка Internal Link

Когда вы закончите работу с экземпляром SDK (например, когда представление закрывается), вызовите cleanup(), чтобы закрыть соединение WebSocket и отменить фоновые задачи:

sdk.cleanup()

Для представлений, управляемых @StateObject из SwiftUI, это обычно вызывается в .onDisappear или когда представление уничтожается.

Загрузка изображений Internal Link

Комментарии

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")
])


Упоминания пользователей Internal Link


Поиск пользователей для автодополнения @mention:

let results = try await sdk.searchUsers(query: "jan")
// Возвращает [UserSearchResult] с userId, username, avatar и т.д.

Встроенный CommentInputBar автоматически обрабатывает автодополнение @mention.



Редактирование и удаление комментариев Internal Link

Редактировать

try await sdk.editComment(commentId: commentId, newText: "Updated text")

Сервер повторно рендерит HTML. Локальный комментарий обновляется автоматически.

Удалить

try await sdk.deleteComment(commentId: commentId)

Удаление комментария также удаляет его потомков из локального дерева.

Обе операции доступны через контекстное меню комментария в интерфейсе, когда текущий пользователь является автором комментария (или администратором сайта).



Обработка ошибок Internal Link

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 -- код ошибки от API
  • reason -- описание ошибки на английском языке
  • translatedError -- локализованное сообщение об ошибке, предоставленное сервером

Blocking errors are also surfaced automatically via sdk.blockingErrorMessage, which the built-in views display to the user.



Локализация Internal Link

Передайте код локали в конфигурации, чтобы локализовать строки, предоставляемые сервером:

let config = FastCommentsWidgetConfig(
    tenantId: "YOUR_TENANT_ID",
    urlId: "my-page",
    locale: "fr_fr"
)

Строки интерфейса на стороне клиента используют локализацию, основанную на iOS-бандлах.

Пример приложения Internal Link

В репозитории содержится полнофункциональное примерное приложение в ExampleApp/ с демонстрациями:

  • Тредовые комментарии с SSO и кастомными темами
  • Социальная лента с созданием записей и фильтрацией по тегам
  • Чат в реальном времени
  • Простые и защищённые SSO-потоки
  • Кастомные кнопки на панели инструментов (для комментариев и ленты)

Нужна помощь?

Если у вас возникнут какие-либо проблемы или вопросы по библиотеке iOS, пожалуйста:

Участие

Вклады приветствуются! Пожалуйста, посетите репозиторий на GitHub для ознакомления с правилами участия.