iOS SDK API Reference
Native Swift SDK with async/await support for iOS 15+ and macOS 12+.
Installation
Swift Package Manager
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/your-org/chatsdk-ios", from: "1.0.0")
]
Or in Xcode: File → Add Package Dependencies → Enter the repository URL.
ChatClient
The main entry point for the iOS SDK.
Initialization
import ChatSDK
let client = ChatClient(
apiURL: URL(string: "https://api.your-server.com")!,
token: "jwt-token"
)
Properties
// Current connection state (Published for SwiftUI)
@Published public private(set) var connectionState: ConnectionState
// Current user (Published for SwiftUI)
@Published public private(set) var currentUser: User?
// API URL
public let apiURL: URL
Connection
connect()
public func connect() async throws
Connect to the chat server and fetch the current user.
do {
try await client.connect()
print("Connected as: \(client.currentUser?.name ?? "")")
} catch {
print("Connection failed: \(error)")
}
disconnect()
public func disconnect()
Disconnect from the server and clean up resources.
updateToken(_:)
public func updateToken(_ newToken: String)
Update the authentication token (for token refresh).
Events
on(_:)
public func on(_ handler: @escaping (ChatEvent) -> Void) -> UUID
Subscribe to chat events. Returns an ID for unsubscribing.
let subscriptionId = client.on { event in
switch event {
case .messageNew(let channelId, let message):
print("New message in \(channelId): \(message.text ?? "")")
case .typingStart(let channelId, let user):
print("\(user.name) is typing...")
default:
break
}
}
off(_:)
public func off(_ id: UUID)
Unsubscribe from events using the subscription ID.
Channels
getChannels(limit:offset:)
public func getChannels(
limit: Int = 20,
offset: Int = 0
) async throws -> [Channel]
Get the user's channels.
let channels = try await client.getChannels(limit: 20)
getChannel(id:)
public func getChannel(id: String) async throws -> Channel
Get a specific channel by ID.
createChannel(type:name:memberIds:)
public func createChannel(
type: Channel.ChannelType = .messaging,
name: String? = nil,
memberIds: [String]
) async throws -> Channel
Create a new channel.
let channel = try await client.createChannel(
name: "Team Chat",
memberIds: ["user-1", "user-2"]
)
Messages
getMessages(channelId:limit:before:after:sinceSeq:)
public func getMessages(
channelId: String,
limit: Int = 50,
before: String? = nil,
after: String? = nil,
sinceSeq: Int? = nil
) async throws -> MessagesResponse
Get messages for a channel.
let response = try await client.getMessages(
channelId: channel.id,
limit: 50
)
print("Loaded \(response.messages.count) messages")
print("Has more: \(response.hasMore)")
sendMessage(channelId:text:attachments:parentId:)
public func sendMessage(
channelId: String,
text: String,
attachments: [Attachment] = [],
parentId: String? = nil
) async throws -> Message
Send a message.
let message = try await client.sendMessage(
channelId: channel.id,
text: "Hello, world!"
)
updateMessage(channelId:messageId:text:)
public func updateMessage(
channelId: String,
messageId: String,
text: String
) async throws -> Message
Update a message.
deleteMessage(channelId:messageId:)
public func deleteMessage(
channelId: String,
messageId: String
) async throws
Delete a message.
Reactions
addReaction(channelId:messageId:emoji:)
public func addReaction(
channelId: String,
messageId: String,
emoji: String
) async throws
Add a reaction to a message.
try await client.addReaction(
channelId: channel.id,
messageId: message.id,
emoji: "👍"
)
removeReaction(channelId:messageId:emoji:)
public func removeReaction(
channelId: String,
messageId: String,
emoji: String
) async throws
Remove a reaction.
Typing Indicators
startTyping(channelId:)
public func startTyping(channelId: String) async throws
Send typing indicator.
stopTyping(channelId:)
public func stopTyping(channelId: String) async throws
Stop typing indicator.
Read Receipts
markAsRead(channelId:messageId:)
public func markAsRead(
channelId: String,
messageId: String
) async throws
Mark messages as read.
Presence
setOnline()
public func setOnline() async throws
Set user as online.
setOffline()
public func setOffline() async throws
Set user as offline.
Threads
getThread(channelId:messageId:limit:)
public func getThread(
channelId: String,
messageId: String,
limit: Int = 50
) async throws -> ThreadResponse
Get thread replies.
replyToThread(channelId:parentMessageId:text:)
public func replyToThread(
channelId: String,
parentMessageId: String,
text: String
) async throws -> Message
Reply to a thread.
Search
searchMessages(query:channelId:limit:)
public func searchMessages(
query: String,
channelId: String? = nil,
limit: Int = 20
) async throws -> [Message]
Search messages.
let results = try await client.searchMessages(
query: "project update",
channelId: channel.id
)
ChatViewModel
SwiftUI-ready view model for managing chat state.
import SwiftUI
import ChatSDK
class ChatViewModel: ObservableObject {
let client: ChatClient
@Published var channels: [Channel] = []
@Published var messages: [String: [Message]] = [:]
@Published var typingUsers: [String: [User]] = [:]
@Published var loading = false
@Published var error: Error?
}
Usage
struct ContentView: View {
@StateObject var viewModel: ChatViewModel
init() {
let client = ChatClient(
apiURL: URL(string: "https://api.example.com")!,
token: "token"
)
_viewModel = StateObject(wrappedValue: ChatViewModel(client: client))
}
var body: some View {
NavigationView {
ChannelListView()
.environmentObject(viewModel)
}
.task {
try? await viewModel.client.connect()
await viewModel.loadChannels()
}
}
}
Types
User
public struct User: Codable, Identifiable, Sendable {
public let id: String
public var name: String
public var image: String?
public var customData: [String: AnyCodable]?
}
Channel
public struct Channel: Codable, Identifiable, Sendable {
public let id: String
public let cid: String
public let type: ChannelType
public var name: String?
public var image: String?
public var memberCount: Int
public var messageCount: Int
public var unreadCount: Int?
public var lastMessage: Message?
public var lastMessageAt: Date?
public var createdAt: Date
public var updatedAt: Date
public enum ChannelType: String, Codable, Sendable {
case messaging
case group
case team
case livestream
}
}
Message
public struct Message: Codable, Identifiable, Sendable {
public let id: String
public let cid: String
public let seq: Int
public var type: MessageType
public var text: String?
public var attachments: [Attachment]
public var user: User?
public var parentId: String?
public var replyToId: String?
public var replyCount: Int
public var reactions: [ReactionGroup]
public var status: MessageStatus
public var createdAt: Date
public var updatedAt: Date?
public var deletedAt: Date?
public enum MessageType: String, Codable, Sendable {
case regular
case deleted
case system
}
public enum MessageStatus: String, Codable, Sendable {
case sending
case sent
case delivered
case read
case failed
}
}
Attachment
public struct Attachment: Codable, Sendable {
public let type: AttachmentType
public var url: String?
public var title: String?
public var mimeType: String?
public var fileSize: Int?
public var width: Int?
public var height: Int?
public var duration: Int?
public var thumbnailUrl: String?
public enum AttachmentType: String, Codable, Sendable {
case image
case video
case audio
case file
case giphy
case voicenote
}
}
ChatEvent
public enum ChatEvent: Sendable {
case connectionChanged(ConnectionState)
case messageNew(channelId: String, message: Message)
case messageUpdated(channelId: String, message: Message)
case messageDeleted(channelId: String, messageId: String)
case reactionAdded(channelId: String, messageId: String, reaction: Reaction)
case reactionRemoved(channelId: String, messageId: String, reaction: Reaction)
case typingStart(channelId: String, user: User)
case typingStop(channelId: String, user: User)
case presenceChanged(userId: String, online: Bool)
case readReceipt(channelId: String, userId: String, messageId: String)
case channelUpdated(channel: Channel)
case memberAdded(channelId: String, member: ChannelMember)
case memberRemoved(channelId: String, userId: String)
}
ConnectionState
public enum ConnectionState: String, Sendable {
case connecting
case connected
case disconnected
case reconnecting
}
ChatError
public enum ChatError: Error, LocalizedError {
case networkError(String)
case apiError(String, code: String?)
case httpError(Int)
case invalidToken
case notConnected
public var errorDescription: String? { ... }
}
Error Handling
do {
let channels = try await client.getChannels()
} catch let error as ChatError {
switch error {
case .networkError(let message):
print("Network error: \(message)")
case .apiError(let message, let code):
print("API error (\(code ?? "unknown")): \(message)")
case .httpError(let statusCode):
print("HTTP error: \(statusCode)")
case .invalidToken:
// Refresh token and reconnect
try await refreshTokenAndReconnect()
case .notConnected:
try await client.connect()
}
}
SwiftUI Examples
Channel List
struct ChannelListView: View {
@EnvironmentObject var viewModel: ChatViewModel
var body: some View {
List(viewModel.channels) { channel in
NavigationLink(destination: ChatView(channelId: channel.id)) {
HStack {
Text(channel.name ?? "Unnamed")
Spacer()
if let unread = channel.unreadCount, unread > 0 {
Text("\(unread)")
.font(.caption)
.padding(4)
.background(Color.blue)
.foregroundColor(.white)
.clipShape(Circle())
}
}
}
}
.task {
await viewModel.loadChannels()
}
}
}
Message List
struct ChatView: View {
let channelId: String
@EnvironmentObject var viewModel: ChatViewModel
@State private var messageText = ""
var messages: [Message] {
viewModel.messages[channelId] ?? []
}
var body: some View {
VStack {
ScrollView {
LazyVStack {
ForEach(messages) { message in
MessageBubble(message: message)
}
}
}
HStack {
TextField("Message", text: $messageText)
.textFieldStyle(.roundedBorder)
Button("Send") {
Task {
await viewModel.sendMessage(
channelId: channelId,
text: messageText
)
messageText = ""
}
}
}
.padding()
}
.task {
await viewModel.loadMessages(channelId: channelId)
}
}
}