
์ธ์ด ๐ฐ๐ท ํ๊ตญ์ด
๋ฌธ์
์์ํ๊ธฐ
์ธ์ฆ
์ฌ์ฉ๋ฒ
Add Comments to Your iOS App
์ด๊ฒ์ FastComments ๊ณต์ iOS ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
iOS ์ฑ์ ์ค์๊ฐ ๋๊ธ, ์ฑํ ๋ฐ ๋ฆฌ๋ทฐ ์์ ฏ์ ์๋ฒ ๋ํ์ธ์.
๋ฆฌํฌ์งํ ๋ฆฌ
๊ธฐ๋ฅ 
- ์ค์ฒฉ๋ ๋ต๊ธ๊ณผ ํ์ด์ง์ ๊ฐ๋ ์ค๋ ๋ํ ๋๊ธ ํธ๋ฆฌ
- ๊ฒ์๋ฌผ ์์ฑ, ๋ฆฌ์ก์ , ๋ฏธ๋์ด ์ฒจ๋ถ๋ฅผ ์ง์ํ๋ ์์ ํผ๋
- ์๋ ์คํฌ๋กค ๋ฐ ๋ ์ง ๊ตฌ๋ถ์๊ฐ ์๋ ๋ผ์ด๋ธ ์ฑํ ๋ชจ๋
- WebSocket์ ํตํ ์ค์๊ฐ ์ ๋ฐ์ดํธ(์ ๋๊ธ, ํฌํ, ์ ์ ์ํ)
- ์ฑ๊ธ ์ฌ์ธ์จ(Simple SSO๋ ํ ์คํธ์ฉ, Secure SSO๋ ์ด์์ฉ)
- ๊ตต๊ฒ, ์ดํค๋ฆญ, ์ฝ๋, @๋ฉ์ ์ ์ง์ํ๋ ๋ฆฌ์น ํ ์คํธ ํธ์ง
- ์/์๋ ํ์ดํ ๋๋ ํํธ ๋ฑ ์ค์ ๊ฐ๋ฅํ ์คํ์ผ์ ํฌํ
- ๋ชจ๋๋ ์ด์ ์์ : ์ ๊ณ , ๊ณ ์ , ์ ๊ธ, ์ฐจ๋จ
- ํ๋ฆฌ์ ๊ณผ ์์ ํ ์ปค์คํฐ๋ง์ด์ง์ ์ ๊ณตํ๋ ์ข ํฉ์ ์ธ ํ ๋ง
- ๋๊ธ ๋ฐ ํผ๋ ๊ฒ์๋ฌผ ์์ฑ์ ์ํ ๋ง์ถค ํด๋ฐ ๋ฒํผ
- ์ด๋ฏธ์ง ์ ๋ก๋
- EU ๋ฆฌ์ ์ง์
- ์ฌ์ฉ์ ์ ์ ์ํ(์จ๋ผ์ธ/์คํ๋ผ์ธ ํ์)
- ํ๊ทธ ๊ธฐ๋ฐ ํผ๋ ํํฐ๋ง
- ํ์งํ ์ง์
์ค์น 
Swift Package Manager๋ฅผ ์ฌ์ฉํ์ฌ FastCommentsUI๋ฅผ ํ๋ก์ ํธ์ ์ถ๊ฐํ์ธ์.
Xcode์์: File > Add Package Dependencies๋ฅผ ์ ํํ ๋ค์ ์ ์ฅ์ URL์ ์ ๋ ฅํ์ธ์.
๋๋ Package.swift์ ์ถ๊ฐํ์ธ์:
dependencies: [
.package(url: "https://github.com/fastcomments/fastcomments-ios.git", from: "1.0.0")
]
๊ทธ๋ฐ ๋ค์ ์ ํ์ ํ๊น์ ์ถ๊ฐํ์ธ์:
.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()
}
}
}
Replace "demo" with your FastComments tenant ID. The urlId identifies the page or thread where comments are stored.
์ธ์ฆ(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-- ์ฌ์ฉ์ ID (์ค์ ํ์ง ์์ผ๋ฉด ์ด๋ฉ์ผ์ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ฌ์ฉ)displayName-- ๋ณ๋์ ํ์ ์ด๋ฆdisplayLabel-- ์ด๋ฆ ์์ ํ์๋๋ ์ฌ์ฉ์ ์ง์ ๋ ์ด๋ธ(์: "VIP")websiteUrl-- ์ฌ์ฉ์ ์ด๋ฆ์ ๋ํ ๋งํฌlocale-- ๋ก์ผ์ผ ์ฝ๋isProfileActivityPrivate-- ํ๋กํ ํ๋ ์จ๊ธฐ๊ธฐ(๊ธฐ๋ณธ๊ฐ: true)
๋ณด์ SSO
ํ๋ก๋์ ์์๋ ๋ฐฑ์๋๊ฐ API ์ํฌ๋ฆฟ์ ์ฌ์ฉํ์ฌ ์๋ช ๋ SSO ํ ํฐ์ ์์ฑํฉ๋๋ค. iOS ์ฑ์ ์ด ํ ํฐ์ ์๋ฒ์์ ๊ฐ์ ธ์์ config์ ์ ๋ฌํฉ๋๋ค.
๋ฐฑ์๋์์ (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()
// ์ด ํ ํฐ์ API๋ฅผ ํตํด iOS ์ฑ์ผ๋ก ๋ฐํํ์ธ์
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()
// ํ ํฐ์ผ๋ก ์ ๊ตฌ์ฑ(config)์ ์์ฑํ๊ฑฐ๋ ๋ก๋ ์ ์ ์ค์ ํ์ธ์
isLoadingToken = false
try? await sdk.load()
}
}
}
SecureSSOUserData๋ ์ถ๊ฐ ํ๋๋ฅผ ์ง์ํฉ๋๋ค:
optedInNotifications-- ์ด๋ฉ์ผ ์๋ฆผ ์์ ๋์displayLabel-- ์ฌ์ฉ์ ์ง์ ๋ ์ด๋ธdisplayName-- ํ์ ์ด๋ฆwebsiteUrl-- ์น์ฌ์ดํธ URLgroupIds-- ๊ทธ๋ฃน ์์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)
| Style | Appearance |
|---|---|
._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()
}
}
}
ํผ๋ ๋ทฐ๋ ์๋์ผ๋ก ํ ํฌ ๋ฆฌํ๋ ์(pull-to-refresh)์ ๋ฌดํ ์คํฌ๋กค์ ํฌํจํฉ๋๋ค.
๊ฒ์๋ฌผ ์์ฑ
๊ฒ์๋ฌผ ์์ฑ ํผ์ ํ์ํ๋ ค๋ฉด FeedPostCreateView๋ฅผ ์ฌ์ฉํ์ธ์:
@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
}
)
}
๊ฒ์๋ฌผ์ ๋ฐ์ํ๊ธฐ
SDK๋ ๋๊ด์ ์ ๋ฐ์ดํธ(optimistic updates)๋ก ๋ฐ์์ ์ฒ๋ฆฌํฉ๋๋ค:
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๋ฅผ ์ฌ์ฉํ์ธ์. ์ด ์ปดํฌ๋ํธ๋ ๋ด๋ถ์ ์ผ๋ก feed SDK์ ๊ตฌ์ฑ(config)์ ์ฌ์ฉํ์ฌ FastCommentsSDK ์ธ์คํด์ค๋ฅผ ์์ฑํฉ๋๋ค:
.sheet(item: $commentsPost) { post in
CommentsSheet(post: post, feedSDK: sdk, onUserClick: { context, userInfo, source in
// ์ฌ์ฉ์ ํด๋ฆญ ์ฒ๋ฆฌ
})
}
์ฐธ๊ณ : FeedPost๊ฐ .sheet(item:)์ ์ฌ์ฉ๋๋ ค๋ฉด Identifiable์ ์ค์ํด์ผ ํฉ๋๋ค. ๋ค์ ์ต์คํ
์
์ ์ถ๊ฐํ์ธ์:
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()
// Later...
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 // ํฌํ ๋ณ๊ฒฝ ์ ์คํ๋ง ์ ๋๋ฉ์ด์
ํ ๋ง ์ ์ฉ
๋ ๊ฐ์ง ๋ฐฉ๋ฒ:
// Via SwiftUI environment (recommended for view hierarchy)
FastCommentsView(sdk: sdk)
.fastCommentsTheme(theme)
// Directly on the SDK
sdk.theme = theme
์ฌ์ฉ์ ์ ์ ํด๋ฐ ๋ฒํผ 
Comment Toolbar Buttons
๋๊ธ ์
๋ ฅ ํด๋ฐ์ ๋ฒํผ์ ์ถ๊ฐํ๋ ค๋ฉด CustomToolbarButton ํ๋กํ ์ฝ์ ๊ตฌํํ์ธ์:
struct EmojiButton: CustomToolbarButton {
let id = "emoji"
let iconSystemName = "face.smiling" // SF ์ฌ๋ณผ ์ด๋ฆ
let contentDescription = "Add Emoji"
let badgeText: String? = nil // ์ ํ์ ๋ฐฐ์ง ์นด์ดํธ
func onClick(text: Binding<String>) {
text.wrappedValue += "\u{1F44D}"
}
// Optional overrides (default to 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)
๋ชจ๋ ์ค์ฌ ์์
์ UI์ ๋๊ธ ์ปจํ
์คํธ ๋ฉ๋ด์์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๊ด๋ฆฌ์ ์์
์ ํ์ฌ ์ฌ์ฉ์๊ฐ ์ฌ์ดํธ ๊ด๋ฆฌ์์ผ ๋๋ง ํ์๋ฉ๋๋ค(SSO์ isAdmin ํ๋๊ทธ ๋๋ ๋์๋ณด๋ ๊ตฌ์ฑ์ผ๋ก ์ค์ ).
์ค์๊ฐ ์
๋ฐ์ดํธ 
sdk.load()๋ฅผ ํธ์ถํ ํ, SDK๋ ๊ตฌ์ฑ๋ urlId์ ๋ํ WebSocket ์ด๋ฒคํธ๋ฅผ ์๋์ผ๋ก ๊ตฌ๋
ํฉ๋๋ค. ๋ค์ ์ด๋ฒคํธ๋ค์ด ์ฒ๋ฆฌ๋ฉ๋๋ค:
- ์๋ก์ด ๋๊ธ, ์์ ๋ฐ ์ญ์
- ํฌํ (์๋ก์ด ํฌํ ๋ฐ ์ทจ์)
- ๊ณ ์ , ์ ๊ธ, ์ ๊ณ ๋ฐ ์ฐจ๋จ ์ํ ๋ณ๊ฒฝ
- ์ฌ์ฉ์ ์ ์(์ ์ฅ/ํด์ฅ)
- ์ค๋ ๋ ์ด๋ฆผ/๋ซํ
- ๋ฐฐ์ง ์์ฌ
- ์๋ฒ ๊ตฌ์ฑ ์ ๋ฐ์ดํธ
์ค์๊ฐ ํ์ ์ ์ด
๊ธฐ๋ณธ์ ์ผ๋ก ๋ค๋ฅธ ์ฌ์ฉ์์ ์ ๋๊ธ์ ์ฆ์ ํ์๋ฉ๋๋ค:
sdk.showLiveRightAway = true // ๊ธฐ๋ณธ๊ฐ: ์ฆ์ ํ์
์ด๋ฅผ false๋ก ์ค์ ํ๋ฉด ์ ๋๊ธ์ด "N๊ฐ์ ์ ๋๊ธ" ๋ฒํผ ๋ค์ ๋ฒํผ๋์ด ์ฌ์ฉ์๊ฐ ์ธ์ ํ์ํ ์ง ์ ํํ ์ ์์ต๋๋ค:
sdk.showLiveRightAway = false
์ฌ์ฉ์ ์ ์
์๋ฒ๊ฐ ์ ์ ์ํ ์ถ์ ์ ํ์ฑํํ๋ฉด ์จ๋ผ์ธ/์คํ๋ผ์ธ ํ์๊ธฐ๊ฐ ์ฌ์ฉ์ ์๋ฐํ์ ์๋์ผ๋ก ํ์๋ฉ๋๋ค. ํด๋ผ์ด์ธํธ์์ ์ถ๊ฐ ๊ตฌ์ฑ์ ํ์ํ์ง ์์ต๋๋ค.
ํ์ด์ง๋ค์ด์

Page Size
// ๋๊ธ: ๊ธฐ๋ณธ๊ฐ 30
sdk.pageSize = 50
// ํผ๋: ๊ธฐ๋ณธ๊ฐ 10
feedSDK.pageSize = 20
Loading More Comments
The UI shows pagination controls automatically. You can also trigger pagination programmatically:
// ๋ค์ ํ์ด์ง ๋ก๋
try await sdk.loadMore()
// ๋ชจ๋ ๋จ์ ํญ๋ชฉ ๋ก๋ (์ฑ๋ฅ ์ ๋๊ธ์ด 2000๊ฐ ์ด๊ณผ์ธ ๊ฒฝ์ฐ ๋นํ์ฑํ๋จ)
try await sdk.loadAll()
// ์ํ ํ์ธ
sdk.hasMore // ๋ ๋ง์ ํ์ด์ง๊ฐ ์๋์ง
sdk.shouldShowLoadAll()
sdk.getCountRemainingToShow()
Child Comment Pagination
์ค์ฒฉ๋ ๋ต๊ธ์ ์ง์ฐ ๋ก๋๋ฉ๋๋ค. ์ฌ์ฉ์๊ฐ ์ค๋ ๋๋ฅผ ํ์ฅํ๋ฉด ์ฒ์ 5๊ฐ์ ์์์ด ๋ก๋๋ฉ๋๋ค. ๋ ๋ง์ ํญ๋ชฉ์ด ์์ผ๋ฉด "๋ ๋ง์ ๋ต๊ธ ๋ถ๋ฌ์ค๊ธฐ" ์ปจํธ๋กค์ด ๋ํ๋ฉ๋๋ค. ์ด ๋์์ UI์์ ์๋์ผ๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค.
์ํ ๋ฐ ๊ด์ฐฐ์ฑ 
Both FastCommentsSDK์ FastCommentsFeedSDK๋ @Published ์์ฑ์ ๊ฐ์ง ObservableObject ํด๋์ค์
๋๋ค. SwiftUI ๋ทฐ์์ ์ด๋ฅผ ๊ด์ฐฐํ์ฌ ๋ฐ์ํ UI ์
๋ฐ์ดํธ๋ฅผ ํ ์ ์์ต๋๋ค.
FastCommentsSDK Published ์์ฑ
| Property | Type | Description |
|---|---|---|
commentCountOnServer |
Int |
์๋ฒ์ ์ ์ฒด ๋๊ธ ์ |
newRootCommentCount |
Int |
๋ฒํผ๋ ์ ๋๊ธ๋ค (showLiveRightAway๊ฐ false์ผ ๋) |
currentUser |
UserSessionInfo? |
ํ์ฌ ์ธ์ฆ๋ ์ฌ์ฉ์ |
isSiteAdmin |
Bool |
ํ์ฌ ์ฌ์ฉ์๊ฐ ์ฌ์ดํธ ๊ด๋ฆฌ์์ธ์ง ์ฌ๋ถ |
isClosed |
Bool |
๋๊ธ ์ค๋ ๋๊ฐ ๋ซํ๋์ง ์ฌ๋ถ |
hasBillingIssue |
Bool |
์ฒญ๊ตฌ ๊ด๋ จ ๋ฌธ์ ๊ฐ ์๋์ง ์ฌ๋ถ |
isLoading |
Bool |
๋คํธ์ํฌ ์์ฒญ์ด ์งํ ์ค์ธ์ง ์ฌ๋ถ |
hasMore |
Bool |
์ถ๊ฐ ๋๊ธ ํ์ด์ง๊ฐ ์กด์ฌํ๋์ง ์ฌ๋ถ |
blockingErrorMessage |
String? |
UI ์๋์ ๋ฐฉํดํ๋ ์ค๋ฅ |
warningMessage |
String? |
๋น์ฐจ๋จ ๊ฒฝ๊ณ ๋ฉ์์ง |
isDemo |
Bool |
๋ฐ๋ชจ ๋ชจ๋๋ก ์คํ ์ค์ธ์ง ์ฌ๋ถ |
commentsVisible |
Bool |
๋๊ธ ํ์ ํ ๊ธ |
toolbarEnabled |
Bool |
์์ ํด๋ฐ๊ฐ ํ์๋๋์ง ์ฌ๋ถ |
FastCommentsFeedSDK Published ์์ฑ
| Property | Type | Description |
|---|---|---|
feedPosts |
[FeedPost] |
ํ์ฌ ๋ก๋๋ ํผ๋ ๊ฒ์๋ฌผ |
hasMore |
Bool |
์ถ๊ฐ ํ์ด์ง๊ฐ ์กด์ฌํ๋์ง ์ฌ๋ถ |
currentUser |
UserSessionInfo? |
ํ์ฌ ์ธ์ฆ๋ ์ฌ์ฉ์ |
blockingErrorMessage |
String? |
์ฐจ๋จ ์ค๋ฅ ๋ฉ์์ง |
isLoading |
Bool |
๋คํธ์ํฌ ์์ฒญ์ด ์งํ ์ค์ธ์ง ์ฌ๋ถ |
newPostsCount |
Int |
๋ง์ง๋ง ๋ก๋ ์ดํ ์ ๊ฒ์๋ฌผ ์ |
๋๊ธ ํธ๋ฆฌ
๋๊ธ ํธ๋ฆฌ๋ sdk.commentsTree๋ฅผ ํตํด ์ ๊ทผํ ์ ์์ต๋๋ค:
// ๋ ๋๋ง์ ์ํ ํ์๋ ๋
ธ๋์ ํํ(Flat) ๋ชฉ๋ก
sdk.commentsTree.visibleNodes
// ID๋ก ๋๊ธ ์กฐํ
sdk.commentsTree.commentsById["comment-id"]
EU ๋ฆฌ์ 
EU ๋ฐ์ดํฐ ์ผํฐ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๊ตฌ์ฑ์ region ํ๋๋ฅผ ์ค์ ํ์ธ์:
let config = FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "my-page",
region: "eu"
)
์ด๋ ๊ฒ ํ๋ฉด ๋ชจ๋ API ์์ฒญ๊ณผ WebSocket ์ฐ๊ฒฐ์ด eu.fastcomments.com์ผ๋ก ๋ผ์ฐํ
๋ฉ๋๋ค.
์ ๋ฆฌ 
SDK ์ธ์คํด์ค ์ฌ์ฉ์ ๋ง์ณค์ ๋(์: ๋ทฐ๊ฐ ๋ซํ๋ ๊ฒฝ์ฐ), WebSocket ์ฐ๊ฒฐ์ ๋ซ๊ณ ๋ฐฑ๊ทธ๋ผ์ด๋ ์์
์ ์ทจ์ํ๋ ค๋ฉด cleanup()์ ํธ์ถํ์ธ์:
sdk.cleanup()
SwiftUI์ @StateObject๋ก ๊ด๋ฆฌ๋๋ ๋ทฐ์ ๊ฒฝ์ฐ, ์ผ๋ฐ์ ์ผ๋ก .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)
๋๊ธ์ ์ญ์ ํ๋ฉด ๊ทธ ๋๊ธ์ ํ์ ๋๊ธ๋ค๋ ๋ก์ปฌ ํธ๋ฆฌ์์ ํจ๊ป ์ ๊ฑฐ๋ฉ๋๋ค.
๋ ์์ ๋ชจ๋ ํ์ฌ ์ฌ์ฉ์๊ฐ ๋๊ธ ์์ฑ์(๋๋ ์ฌ์ดํธ ๊ด๋ฆฌ์)์ธ ๊ฒฝ์ฐ UI์ ๋๊ธ ์ปจํ ์คํธ ๋ฉ๋ด๋ฅผ ํตํด ์ด์ฉํ ์ ์์ต๋๋ค.
์ค๋ฅ ์ฒ๋ฆฌ 
SDK ๋ฉ์๋๋ FastCommentsError๋ฅผ ๋์ง๋ฉฐ, ์ด๋ LocalizedError๋ฅผ ์ค์ํฉ๋๋ค:
do {
try await sdk.load()
} catch let error as FastCommentsError {
print(error.translatedError ?? error.reason ?? "Unknown error")
} catch {
print(error.localizedDescription)
}
FastCommentsError ์์ฑ:
code-- API์ ์ค๋ฅ ์ฝ๋reason-- ์์ด ์ค๋ฅ ์ค๋ชtranslatedError-- ์๋ฒ์์ ์ ๊ณตํ ํ์งํ๋ ์ค๋ฅ ๋ฉ์์ง
์ฐจ๋จ ์ค๋ฅ๋ ๋ํ sdk.blockingErrorMessage๋ฅผ ํตํด ์๋์ผ๋ก ๋
ธ์ถ๋๋ฉฐ, ๋ด์ฅ ๋ทฐ๊ฐ ์ด๋ฅผ ์ฌ์ฉ์์๊ฒ ํ์ํฉ๋๋ค.
ํ์งํ 
์๋ฒ ์ ๊ณต ๋ฌธ์์ด์ ํ์งํํ๋ ค๋ฉด ๊ตฌ์ฑ์ ๋ก์ผ์ผ ์ฝ๋๋ฅผ ์ ๋ฌํ์ธ์:
let config = FastCommentsWidgetConfig(
tenantId: "YOUR_TENANT_ID",
urlId: "my-page",
locale: "fr_fr"
)
ํด๋ผ์ด์ธํธ ์ธก UI ๋ฌธ์์ด์ iOS ๋ฒ๋ค ๊ธฐ๋ฐ์ ํ์งํ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
์์ ์ฑ 
๋ ํฌ์งํ ๋ฆฌ์๋ ExampleApp/์ ์ ์ฒด ์์ ์ฑ์ด ํฌํจ๋์ด ์์ผ๋ฉฐ ๋ค์์ ์์ฐํฉ๋๋ค:
- SSO ๋ฐ ์ปค์คํ ํ ๋ง๋ฅผ ์ฌ์ฉํ๋ ์ค๋ ๋ํ ๋๊ธ
- ๊ฒ์๋ฌผ ์์ฑ ๋ฐ ํ๊ทธ ํํฐ๋ง์ด ๊ฐ๋ฅํ ์์ ํผ๋
- ๋ผ์ด๋ธ ์ฑํ
- ๊ฐ๋จํ๊ณ ์์ ํ SSO ํ๋ฆ
- ๋ง์ถค ํด๋ฐ ๋ฒํผ(๋๊ธ ๋ฐ ํผ๋)
๋์์ ๋ฐ์ผ์๊ฒ ์ด์?
iOS ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ฑฐ๋ ์ง๋ฌธ์ด ์๋ ๊ฒฝ์ฐ, ๋ค์์ ์ด์ฉํด ์ฃผ์ธ์:
๊ธฐ์ฌํ๊ธฐ
๊ธฐ์ฌ๋ฅผ ํ์ํฉ๋๋ค! ๊ธฐ์ฌ ์ง์นจ์ GitHub ์ ์ฅ์๋ฅผ ๋ฐฉ๋ฌธํ์ธ์.