FastComments.com

Add Comments to Your iOS App

Esta é a biblioteca iOS oficial do FastComments.

Incorpore widgets de comentários ao vivo, chat e avaliação no seu app iOS.

Repositório

Ver no GitHub


Recursos Internal Link

  • Árvores de comentários encadeados com respostas aninhadas e paginação
  • Feed social com criação de postagens, reações e anexos de mídia
  • Modo de chat ao vivo com rolagem automática e separadores de data
  • Atualizações em tempo real via WebSocket (novos comentários, votos, presença)
  • Single Sign-On (SSO simples para testes, SSO seguro para produção)
  • Edição de texto rico com negrito, itálico, código e menções @
  • Votação com estilos configuráveis (setas para cima/baixo ou corações)
  • Ações de moderação: sinalizar, fixar, trancar, bloquear
  • Temas abrangentes com predefinições e personalização completa
  • Botões de barra de ferramentas personalizados para criação de comentários e publicações no feed
  • Upload de imagens
  • Suporte à região da UE
  • Presença do usuário (indicadores online/offline)
  • Filtragem do feed baseada em tags
  • Suporte à localização

Requisitos Internal Link


  • iOS 16+ ou macOS 14+
  • Swift 5.9+
  • SwiftUI

Instalação Internal Link


Adicione FastCommentsUI ao seu projeto usando o Swift Package Manager.

No Xcode: Arquivo > Adicionar Dependências de Pacote, em seguida insira a URL do repositório.

Ou adicione-o ao seu Package.swift:

dependencies: [
    .package(url: "https://github.com/fastcomments/fastcomments-ios.git", from: "1.0.0")
]

Em seguida, adicione o produto ao seu target:

.target(
    name: "YourApp",
    dependencies: [
        .product(name: "FastCommentsUI", package: "fastcomments-ios")
    ]
)

Importe ambos os módulos onde necessário:

import FastCommentsUI
import FastCommentsSwift

Início Rápido Internal Link

A configuração mínima para exibir um widget de comentários:

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

Replace "demo" with your FastComments tenant ID. The urlId identifies the page or thread where comments are stored.



Autenticação (SSO) Internal Link

FastComments suporta três modos de autenticação:

  1. Anônimo -- sem token SSO; os usuários recebem identidades baseadas em sessão
  2. Simple SSO -- token no cliente para demonstrações e testes (não seguro)
  3. Secure SSO -- token assinado pelo servidor para produção

Simple SSO

Útil para demonstrações e testes locais. Qualquer pessoa pode se passar por qualquer usuário com Simple SSO, portanto não o utilize em produção.

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 também suporta campos opcionais:

  • id -- ID do usuário (padrão é o email se não definido)
  • displayName -- nome de exibição separado
  • displayLabel -- etiqueta personalizada exibida ao lado do nome (por exemplo "VIP")
  • websiteUrl -- link no nome do usuário
  • locale -- código de localidade
  • isProfileActivityPrivate -- ocultar atividade do perfil (padrão é true)

Secure SSO

Em produção, seu backend gera um token SSO assinado usando seu segredo de API. O app iOS busca esse token no seu servidor e o passa para a config.

No seu backend (usando o SDK Swift do FastComments ou qualquer linguagem):

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()
// Retorne este token para seu app iOS via sua API

No seu app 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 {
            // Busque o token no seu backend
            let token = try? await fetchSSOTokenFromYourBackend()
            // Crie uma nova config com o token, ou defina-o antes do carregamento
            isLoadingToken = false
            try? await sdk.load()
        }
    }
}

SecureSSOUserData suporta campos adicionais:

  • optedInNotifications -- opt-in para notificações por email
  • displayLabel -- etiqueta personalizada
  • displayName -- nome de exibição
  • websiteUrl -- URL do site
  • groupIds -- associações a grupos
  • isAdmin -- privilégios de administrador
  • isModerator -- privilégios de moderador
  • isProfileActivityPrivate -- privacidade do perfil


Comentários encadeados Internal Link

Uso Básico

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

Estilos de Voto

O estilo de voto padrão mostra setas para cima/para baixo. Passe ._1 para votos em forma de coração:

FastCommentsView(sdk: sdk, voteStyle: ._1)
Estilo Aparência
._0 Botões de seta para cima/baixo com contagem líquida
._1 Único botão de coração com contagem

Callbacks de Evento

Use callbacks no estilo de modificador para lidar com interações do usuário:

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 ou .avatar
        print("Tapped \(userInfo.displayName)")
    }

Aplicando um Tema

Passe um tema através do ambiente SwiftUI:

FastCommentsView(sdk: sdk)
    .fastCommentsTheme(myTheme)
    .task { try? await sdk.load() }

Ou defina-o diretamente no SDK:

sdk.theme = FastCommentsTheme.modern

Direção de Ordenação

sdk.defaultSortDirection = .nf  // Mais recentes primeiro (padrão)
sdk.defaultSortDirection = .of  // Mais antigos primeiro
sdk.defaultSortDirection = .mr  // Mais relevantes

Chat ao Vivo Internal Link

LiveChatView fornece uma experiência de chat em tempo real com rolagem automática, separadores de data e um layout compacto. Ele configura automaticamente o SDK para ordenação do mais antigo para o mais recente e exibição ao vivo imediata.

struct ChatView: View {
    @StateObject private var sdk: FastCommentsSDK = {
        let config = FastCommentsWidgetConfig(
            tenantId: "YOUR_TENANT_ID",
            urlId: "chat-room-1",
            sso: ssoToken  // SSO recomendado para que os usuários tenham nomes
        )
        return FastCommentsSDK(config: config)
    }()

    var body: some View {
        LiveChatView(sdk: sdk)
            .onCommentPosted { comment in
                print("Sent: \(comment.commentHTML)")
            }
            .task {
                try? await sdk.load()
            }
    }
}

LiveChatView suporta estes callbacks:

  • .onCommentPosted -- disparado quando o usuário envia uma mensagem
  • .onCommentDeleted -- disparado quando uma mensagem é excluída
  • .onUserClick -- disparado quando o nome ou avatar de um usuário é tocado


Feed Social Internal Link

O sistema de feed é um SDK separado (FastCommentsFeedSDK) com sua própria view.

Carregando e Exibindo o Feed

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
                // Apresentar folha de compartilhamento
            }
            .onUserClick { context, userInfo, source in
                // Navegar para o perfil do usuário
            }
            .onMediaClick { mediaItem, index in
                // Apresentar visualizador de imagem em tela cheia
            }
            .task {
                try? await sdk.load()
            }
    }
}

A visualização do feed inclui pull-to-refresh e rolagem infinita automaticamente.

Criando Posts

Use FeedPostCreateView para apresentar um formulário de criação de post:

@State private var showCreatePost = false

// In your view body:
.sheet(isPresented: $showCreatePost) {
    FeedPostCreateView(
        sdk: sdk,
        onPostCreated: { post in
            showCreatePost = false
            Task { try? await sdk.refresh() }
        },
        onCancelled: {
            showCreatePost = false
        }
    )
}

Reagindo a Posts

O SDK lida com reações com atualizações otimistas:

try await sdk.reactPost(postId: post.id, reactionType: "l")

// Check reaction state
let hasLiked = sdk.hasUserReacted(postId: post.id, reactType: "l")
let likeCount = sdk.getLikeCount(postId: post.id)

Abrindo Comentários em um Post

Use CommentsSheet para exibir comentários de um post do feed. Ele cria internamente uma instância de FastCommentsSDK usando a config do feed SDK:

.sheet(item: $commentsPost) { post in
    CommentsSheet(post: post, feedSDK: sdk, onUserClick: { context, userInfo, source in
        // Lidar com clique do usuário
    })
}

Nota: FeedPost deve conformar ao Identifiable para .sheet(item:). Adicione esta extensão:

extension FeedPost: @retroactive Identifiable {}

Filtragem do Feed por Tags

Implemente o protocolo TagSupplier para filtrar posts do feed por tags:

struct TeamTagSupplier: TagSupplier {
    func getTags(currentUser: UserSessionInfo?) -> [String]? {
        guard let user = currentUser else { return nil }
        return ["team:\(user.id ?? "")", "public"]
    }
}

sdk.tagSupplier = TeamTagSupplier()

Retorne nil para um feed global sem filtro.

Salvando e Restaurando o Estado do Feed

Preserve o estado de paginação durante eventos do ciclo de vida da view:

let state = sdk.savePaginationState()
// Later...
sdk.restorePaginationState(state)

Excluindo Posts

sdk.onPostDeleted = { postId in
    print("Post \(postId) was deleted")
}


Temas Internal Link

Predefinições de Tema

Quatro predefinições integradas estão disponíveis:

// Padrões do sistema
sdk.theme = FastCommentsTheme.default

// Cartões com sombras e cantos grandes arredondados
sdk.theme = FastCommentsTheme.modern

// Plano, sem sombras, raio de canto pequeno, sem linhas de encadeamento
sdk.theme = FastCommentsTheme.minimal

// Define todas as cores de ação para uma única cor da marca
sdk.theme = FastCommentsTheme.allPrimary(.indigo)

Estilos de Exibição de Comentários

var theme = FastCommentsTheme()
theme.commentStyle = .flat    // Lista plana com divisores (padrão)
theme.commentStyle = .card    // Cartões arredondados com sombras
theme.commentStyle = .bubble  // Estilo de balão de conversa

Cores

Todas as propriedades de cor são opcionais. Valores não definidos usarão os padrões sensíveis do sistema.

var theme = FastCommentsTheme()

// Cores da marca
theme.primaryColor = .indigo
theme.primaryLightColor = .indigo.opacity(0.6)
theme.primaryDarkColor = Color(red: 0.2, green: 0.1, blue: 0.5)

// Fundos
theme.commentBackgroundColor = Color(.secondarySystemGroupedBackground)
theme.containerBackgroundColor = Color(.systemGroupedBackground)

// Botões de ação
theme.actionButtonColor = .indigo
theme.replyButtonColor = .indigo
theme.toggleRepliesButtonColor = .indigo.opacity(0.8)
theme.loadMoreButtonTextColor = .indigo

// Votos
theme.voteActiveColor = .red
theme.voteCountColor = .primary
theme.voteCountZeroColor = .secondary
theme.voteDividerColor = Color(.separator)

// Links
theme.linkColor = .indigo
theme.linkColorPressed = .indigo.opacity(0.5)

// Diálogos
theme.dialogHeaderBackgroundColor = .indigo
theme.dialogHeaderTextColor = .white

// Barra de entrada
theme.inputBarBackgroundColor = Color(.systemBackground)
theme.inputBarBorderColor = Color(.separator)

// Outros
theme.onlineIndicatorColor = .green
theme.separatorColor = Color(.separator)
theme.badgeBackgroundColor = .gray.opacity(0.2)
theme.threadLineColor = .indigo.opacity(0.15)

Tipografia

theme.commenterNameFont = .subheadline.weight(.bold)
theme.bodyFont = .body
theme.captionFont = .caption
theme.actionFont = .caption.weight(.medium)

Layout e Espaçamento

theme.cornerRadius = .large       // .none, .small, .medium, .large
theme.commentSpacing = 4          // Pontos entre linhas de comentário
theme.nestingIndent = 20          // Pontos de indentação por nível de aninhamento
theme.avatarSize = 36             // Diâmetro do avatar para comentários raiz
theme.replyAvatarSize = 28        // Diâmetro do avatar para respostas aninhadas

Efeitos Visuais

theme.showShadows = true          // Sombras sutis nos cartões
theme.showThreadLine = true       // Linha vertical conectando respostas aninhadas
theme.animateVotes = true         // Animação com efeito de mola nas mudanças de voto

Aplicando Temas

Duas abordagens:

// Via SwiftUI environment (recommended for view hierarchy)
FastCommentsView(sdk: sdk)
    .fastCommentsTheme(theme)

// Directly on the SDK
sdk.theme = theme


Botões personalizados da barra de ferramentas Internal Link

Botões da Barra de Ferramentas de Comentários

Implemente o protocolo CustomToolbarButton para adicionar botões à barra de ferramentas do campo de entrada de comentários:

struct EmojiButton: CustomToolbarButton {
    let id = "emoji"
    let iconSystemName = "face.smiling"       // nome do SF Symbol
    let contentDescription = "Add Emoji"
    let badgeText: String? = nil              // Contador de badge opcional

    func onClick(text: Binding<String>) {
        text.wrappedValue += "\u{1F44D}"
    }

    // Sobrescritas opcionais (padrão true)
    func isEnabled() -> Bool { true }
    func isVisible() -> Bool { true }
}

Passe botões customizados ao criar a view:

FastCommentsView(
    sdk: sdk,
    customToolbarButtons: [EmojiButton(), CodeBlockButton()]
)

Ou adicione-os globalmente no SDK (aplica-se a todas as instâncias):

sdk.addGlobalCustomToolbarButton(EmojiButton())
sdk.removeGlobalCustomToolbarButton(id: "emoji")
sdk.clearGlobalCustomToolbarButtons()

Botões da Barra de Ferramentas do Feed

Implemente FeedCustomToolbarButton para o formulário de criação de post:

struct HashtagButton: FeedCustomToolbarButton {
    let id = "hashtag"
    let iconSystemName = "number"
    let contentDescription = "Add Hashtag"

    func onClick(content: Binding<String>) {
        content.wrappedValue += "#"
    }
}

Passe-os para a view de criação:

FeedPostCreateView(
    sdk: sdk,
    customToolbarButtons: [HashtagButton()],
    onPostCreated: { _ in },
    onCancelled: { }
)

Ou defina-os globalmente no SDK do feed:

sdk.globalFeedToolbarButtons = [HashtagButton()]


Moderação Internal Link

Ações disponíveis para todos os usuários

  • Denunciar/Remover denúncia -- denunciar um comentário para revisão
try await sdk.flagComment(commentId: commentId)
try await sdk.unflagComment(commentId: commentId)
  • Bloquear/Desbloquear -- ocultar todos os comentários de um usuário (por visualizador)
try await sdk.blockUser(commentId: commentId)
try await sdk.unblockUser(commentId: commentId)

Ações apenas para administradores

  • Fixar/Desfixar -- fixar um comentário no topo da thread
try await sdk.pinComment(commentId: commentId)
try await sdk.unpinComment(commentId: commentId)
  • Bloquear/Desbloquear -- impedir novas respostas a um comentário
try await sdk.lockComment(commentId: commentId)
try await sdk.unlockComment(commentId: commentId)

Todas as ações de moderação também estão disponíveis através do menu de contexto do comentário na interface. As ações de administrador aparecem apenas quando o usuário atual é um administrador do site (definido via SSO isAdmin flag ou configuração do painel).



Atualizações em Tempo Real Internal Link

Após chamar sdk.load(), o SDK inscreve-se automaticamente em eventos WebSocket para o urlId configurado. Os seguintes eventos são tratados:

  • Novos comentários, edições e exclusões
  • Votos (novos e removidos)
  • Alterações de estado de pin, lock, flag e block
  • Presença do usuário (entrada/saída)
  • Abertura/fechamento de thread
  • Concessões de badges
  • Atualizações de configuração do servidor

Controlando a exibição ao vivo

Por padrão, novos comentários de outros usuários aparecem imediatamente:

sdk.showLiveRightAway = true   // Padrão: mostrar instantaneamente

Defina isso como false para armazenar novos comentários atrás de um botão 'N novos comentários', permitindo que o usuário escolha quando revelá-los:

sdk.showLiveRightAway = false

Presença do usuário

Indicadores online/offline aparecem automaticamente nos avatares dos usuários quando o servidor habilita o rastreamento de presença. Nenhuma configuração adicional é necessária no cliente.



Paginação Internal Link

Page Size

// Comentários: padrão 30
sdk.pageSize = 50

// Feed: padrão 10
feedSDK.pageSize = 20

Loading More Comments

A IU exibe controles de paginação automaticamente. Você também pode acionar a paginação programaticamente:

// Carregar próxima página
try await sdk.loadMore()

// Carregar todos os restantes (desativado se >2000 comentários por motivos de desempenho)
try await sdk.loadAll()

// Verificar estado
sdk.hasMore            // Se há mais páginas
sdk.shouldShowLoadAll()
sdk.getCountRemainingToShow()

Child Comment Pagination

As respostas aninhadas são carregadas sob demanda. Quando um usuário expande um tópico, os primeiros 5 comentários filhos são carregados. Um controle "carregar mais respostas" aparece se houver mais. Isso é tratado automaticamente pela IU.

Estado e Observabilidade Internal Link

Tanto FastCommentsSDK quanto FastCommentsFeedSDK são classes ObservableObject com propriedades @Published. Você pode observar essas propriedades nas suas views SwiftUI para atualizações reativas da UI.

Propriedades Publicadas do FastCommentsSDK

Propriedade Tipo Descrição
commentCountOnServer Int Contagem total de comentários no servidor
newRootCommentCount Int Comentários novos em buffer (quando showLiveRightAway é false)
currentUser UserSessionInfo? Usuário autenticado atual
isSiteAdmin Bool Se o usuário atual é administrador do site
isClosed Bool Se o tópico de comentários está fechado
hasBillingIssue Bool Se há um problema de cobrança
isLoading Bool Se uma requisição de rede está em andamento
hasMore Bool Se existem mais páginas de comentários
blockingErrorMessage String? Erro que impede o funcionamento da UI
warningMessage String? Mensagem de aviso não bloqueante
isDemo Bool Se está em modo de demonstração
commentsVisible Bool Alterna a visibilidade dos comentários
toolbarEnabled Bool Se a barra de ferramentas de formatação está exibida

Propriedades Publicadas do FastCommentsFeedSDK

Propriedade Tipo Descrição
feedPosts [FeedPost] Posts do feed carregados atualmente
hasMore Bool Se existem mais páginas
currentUser UserSessionInfo? Usuário autenticado atual
blockingErrorMessage String? Mensagem de erro bloqueante
isLoading Bool Se uma requisição de rede está em andamento
newPostsCount Int Número de novos posts desde o último carregamento

Árvore de Comentários

A árvore de comentários é acessível via sdk.commentsTree:

// Lista plana de nós visíveis para renderização
sdk.commentsTree.visibleNodes

// Localiza um comentário pelo ID
sdk.commentsTree.commentsById["comment-id"]


Região da UE Internal Link


Para usar o datacenter da UE, defina o campo region na sua configuração:

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

Isso direciona todas as requisições de API e conexões WebSocket para eu.fastcomments.com.



Limpeza Internal Link

Quando você terminar com uma instância do SDK (por exemplo, quando a view estiver sendo dispensada), chame cleanup() para fechar a conexão WebSocket e cancelar tarefas em segundo plano:

sdk.cleanup()

Para views gerenciadas pelo @StateObject do SwiftUI, isso normalmente é chamado em .onDisappear ou quando a view é desalocada.

Envio de imagens Internal Link

Comentários

let imageUrl = try await sdk.uploadImage(imageData: jpegData, filename: "photo.jpg")

Retorna a string de URL da imagem enviada.

Publicações do Feed

let mediaItem = try await feedSDK.uploadImage(imageData: jpegData, filename: "photo.jpg")

// Fazer upload de várias imagens em paralelo
let mediaItems = try await feedSDK.uploadImages(images: [
    (jpegData1, "photo1.jpg"),
    (jpegData2, "photo2.jpg")
])


Menções a usuários Internal Link

Pesquisar usuários para dar suporte ao autocompletar de @mention:

let results = try await sdk.searchUsers(query: "jan")
// Retorna [UserSearchResult] com userId, username, avatar, etc.

O CommentInputBar integrado lida com o autocompletar de @mention automaticamente.

Edição e exclusão de comentários Internal Link

Editar

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

O servidor renderiza novamente o HTML. O comentário local é atualizado automaticamente.

Excluir

try await sdk.deleteComment(commentId: commentId)

Ao excluir um comentário, seus descendentes também são removidos da árvore local.

Ambas as ações estão disponíveis através do menu de contexto do comentário na interface quando o usuário atual for o autor do comentário (ou um administrador do site).



Tratamento de erros Internal Link


Os métodos do SDK lançam FastCommentsError, que está em conformidade com LocalizedError:

do {
    try await sdk.load()
} catch let error as FastCommentsError {
    print(error.translatedError ?? error.reason ?? "Unknown error")
} catch {
    print(error.localizedDescription)
}

Propriedades de FastCommentsError:

  • code -- código de erro da API
  • reason -- descrição do erro em inglês
  • translatedError -- mensagem de erro localizada fornecida pelo servidor

Erros de bloqueio também são exibidos automaticamente via sdk.blockingErrorMessage, que as visualizações integradas exibem para o usuário.



Localização Internal Link


Passe um código de localidade na configuração para localizar as strings fornecidas pelo servidor:

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

As strings da interface do usuário do lado do cliente utilizam a localização baseada em bundles do iOS.



Aplicativo de exemplo Internal Link

O repositório inclui um aplicativo de exemplo completo em ExampleApp/ com demonstrações de:

  • Comentários encadeados com SSO e temas personalizados
  • Feed social com criação de postagens e filtragem por tags
  • Bate-papo ao vivo
  • Fluxos SSO simples e seguros
  • Botões personalizados na barra de ferramentas (comentários e feed)

Precisa de ajuda?

Se você encontrar algum problema ou tiver dúvidas sobre a biblioteca iOS, por favor:

Contribuindo

Contribuições são bem-vindas! Por favor visite o repositório do GitHub para as diretrizes de contribuição.