Quick Start

SendBird enables you to add real-time chat to any app with speed and efficiency. The iOS SDK specifically provides you with methods to initialize and configure chat from the client-side - no backend required. This Quick Start section will present a succinct overview of the SDK’s functionalities and logic, then guide you through the preliminary steps of setting up SendBird in your own app.

Step 1: Create a new SendBird application in the Dashboard

The first thing you need to do is to log in to the SendBird Dashboard and create a SendBird application. If you do not yet have an account, you can log in with Google, GitHub, or create a new account.

You should create one application per service, regardless of the platform. For example, an app released in both Android and iOS would require only one application to be created in the Dashboard.

All users within the same SendBird application are able to communicate with each other, across all platforms. This means users using iOS, Android, web clients, etc. can all chat with one another. However, users in different SendBird applications cannot talk to each other.

Step 2-1: When building a new app

If you are developing an application from scratch, a convenient way to begin is by building your app on top of our Sample UI project.

You can open the sample project from Xcode or by running the command below.

Make sure that you open SendBird-iOS.xcworkspace, not SendBird-iOS.xcodeproj

$ open sample-objc/SendBird-iOS.xcworkspace // Objective-C

$ open sample-swift/SendBird-iOS.xcworkspace // Swift

Build and run the Sample UI project to play around with Open Chat or Group Chat.

The sample project is shipped with a Testing App ID.
You must replace this with your own App ID, which can be found in the SendBird Dashboard, for a production app.

Step 2-2: When integrating SendBird with an existing app

Install SendBird SDK using CocoaPods.

Add the lines below to your Podfile.

platform :ios, '8.0'

target 'YourProject' do
  use_frameworks!

  pod 'SendBirdSDK'
end

Install the SendBird framework through CocoaPods.

$ pod install

Now you can run your project with the SendBird Framework by opening *YOUR_PROJECT*.xcworkspace.

If you do not wish to use CocoaPods, check out the manual installation guide.

Install SendBird SDK using Carthage.

  1. Add github "smilefam/sendbird-ios-framework" to your Cartfile.
  2. Run carthage update.
  3. Go to your Xcode project's General settings. Open <YOUR_XCODE_PROJECT_DIRECTORY>/Carthage/Build/iOS in Finder and drag SendBirdSDK.framework to the Embedded Binaries section in Xcode. Make sure the Copy items if needed option is selected and click Finish.

Enabling ARC

Enabling Automatic Reference Counting (ARC) is necessary in order to use the SendBird framework. Go to your project's Build Settings, then set the value of Objective-C Automatic Reference Counting to Yes (in Swift, this setting is set to Yes by default).

If you do not want to enable ARC in a project-wide scope, then navigate to Build Phases - Compile Sources and add -fobjc-arc to the Compiler Flags in the source file that will be using the SendBird framework. This will ensure that ARC will be enabled only for that file.

Step 3: Using the SendBird SDK in Swift

You can access all classes and methods with just one import statement - without a bridging header file - in both Objective-C and Swift.

Objective-C
#import <SendBirdSDK/SendBirdSDK.h> // Objective-C
Swift
import SendBirdSDK  // Swift

The guide below might help you with understanding how to use the SendBird SDK in Swift syntax.

Interacting with Objective-C APIs in Swift

Authentication

Initializing with APP_ID

To use its chat features, you must initialize SendBird using the APP_ID assigned to your SendBird application.
Typically, initialization would be implemented in the user login view controller.

Objective-C
[SBDMain initWithApplicationId:APP_ID];
Swift
SBDMain.initWithApplicationId(APP_ID)

Connecting with UserID

By default, SendBird requires only a UserID to join a channel. Upon requesting connection, SendBird will query its user database for a matching UserID. If it finds that the UserID has not been registered yet, a new user account will be created. The UserID can be any unique string id, such as an email address or a UID from your database.

This simple authentication procedure might be useful when you are in development or if your service does not require additional security.

Explanation on SendBird's usage of Delegates and callbacks can be found under the Event Handler section.

Objective-C
[SBDMain connectWithUserId:USER_ID completionHandler:^(SBDUser * _Nullable user, SBDError * _Nullable error) {
    // ...
}];
Swift
SBDMain.connect(withUserId: USER_ID, completionHandler: { (user, error) in 
    // ...
})

Connecting with UserID and Access Token

With the SendBird Platform API, you can create a user with an access token, or you can issue an access token for an existing user. Once an access token is issued, you are required to provide the user's token in the login method.

  1. Create a SendBird user account via the Platform API when your user signs up on your service.
  2. Save the access token to your secured persistent store.
  3. Load the access token in your client and pass it to the SendBird login method.
  4. For security reasons, we recommend that you periodically update your access token by issuing a new token to replace the previous one.

You can set restrictions for users without access tokens in your Dashboard settings. These settings can be found under Security - Access Token Policy.

Objective-C
[SBDMain connectWithUserId:USER_ID accessToken:ACCESS_TOKEN completionHandler:^(SBDUser * _Nullable user, SBDError * _Nullable error) {
    // ...
}];
Swift
SBDMain.connect(withUserId: USER_ID, accessToken: ACCESS_TOKEN, completionHandler: { (user, error) in
    // ...
})

Disconnecting

You should disconnect from SendBird when your user no longer needs to receive messages from an online state.

Users will still be able to receive Group Channel messages through Push Notifications.

Disconnecting removes all registered handlers and callbacks. That is, it removes all Event Handlers added through addChannelDelegate:identifier: or addConnectionDelegate:identifier: of SBDMain. It also flushes all internally cached data, such as the channels that are cached when getChannelWithUrl:completionHandler: of SBDOpenChannel or getChannelWithUrl:completionHandler: of SBDGroupChannel is called.

Objective-C
[SBDMain disconnectWithCompletionHandler:^{
    // ...
}];
Swift
SBDMain.disconnect(completionHandler: {
    // ...
})

Updating a User Profile and Profile Image

You can update a user's nickname and profile image.

Call updateCurrentUserInfoWithNickName to update a user's nickname, as well as their profile picture with a URL.

Objective-C
[SBDMain connectWithUserId:USER_ID completionHandler:^(SBDUser * _Nullable user, SBDError * _Nullable error) {
    [SBDMain updateCurrentUserInfoWithNickname:NICKNAME profileUrl:PROFILE_URL completionHandler:^(SBDError * _Nullable error) {
        // ...
    }];
}];
Swift
SBDMain.connect(withUserId: USER_ID, completionHandler: { (user, error) in 
    SBDMain.updateCurrentUserInfo(withNickname: NICKNAME, profileUrl: PROFILE_URL, completionHandler: { (error) in
        // ...
    })
})

Or, you can pass in an image file directly.

Objective-C
[SBDMain connectWithUserId:USER_ID completionHandler:^(SBDUser * _Nullable user, SBDError * _Nullable error) {
    [SBDMain updateCurrentUserInfoWithNickname:NICKNAME profileImage:PROFILE_FILE completionHandler:^(SBDError * _Nullable error) {
        // ...
    }];
}];
Swift
SBDMain.connect(withUserId: USER_ID, completionHandler: { (user, error) in 
    SBDMain.updateCurrentUserInfo(withNickname: NICKNAME, profileImage: PROFILE_FILE, completionHandler: { (error) in
        // ...
    })
})

Channel Types

You should understand the following terminology before proceeding with the rest of this guide.

Open Channel

An Open Channel is a public chat. In this channel type, anyone can enter and participate in the chat without permission. A single channel can handle thousands of simultaneous users. i.e.) a Twitch-style public chat

Group Channel

A Group Channel is a private chat. A user may join the chat only through an invitation by another user who is already a member of the chatroom.

  • Distinct property : A channel with the Distinct property enabled will always be reused for the same members. If a new member is invited, or if a member leaves the channel, then the Distinct property is disabled automatically.

  • 1-on-1 messaging : 1-on-1 messaging is a private channel between two users. You can enable the Distinct property for the channel in order to reuse a channel for the same members. i.e.) Twitter Direct Messages-style 1-on-1 chatting

  • Group messaging : Group messaging is a private channel among multiple users. You can invite up to hundreds of members into a group channel. i.e.) a WhatsApp-style closed group chat

Open vs. Group Channels

Type Open Channel Group Channel
Access Control Public Invitation required
Class Name OpenChannel GroupChannel
Number of Members Over a few thousand Less than a few hundred
How to Create SendBird Dashboard / Platform API / Client SDK Client SDK / Platform API
Operators Supported N/A
User Ban Supported N/A
User Mute Supported N/A
Freeze Channel Supported N/A
Push Notifications N/A Supported
Unread Counts N/A Supported
Read Receipts N/A Supported
Typing Indicators N/A Supported

Open Channel

An Open Channel is a public chat. In this channel type, anyone can enter and participate in the chat without permission. A single channel can handle thousands of simultaneous users. i.e.) a Twitch-style public chat

Creating an Open Channel

Open Channel is ideal for use cases that require a small and static number of channel. To create an open channel from the SendBird Dashboard, do the following.

In the dashboard, click OPEN CHANNELS, then click CREATE at the top-left corner. In the dialog box that appears, specify the name, url, cover image, custom type of a channel. The channel url is a unique identifier.

You can also create a channel via the SDK or the SendBird Platform API. You should do so when your channel needs to be created on demand or dynamically.

Objective-C
[SBDOpenChannel createChannelWithName:NAME coverUrl:COVER_URL data:DATA operatorUserIds:nil completionHandler:^(SBDOpenChannel * _Nullable channel, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...
}];
Swift
SBDOpenChannel.createChannel(withName: NAME, coverUrl: COVER_URL, data: DATA, operatorUserIds: nil, completionHandler: { (channel, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...
})

You can also append information by passing additional arguments.

Objective-C
[SBDOpenChannel createChannelWithName:NAME coverImage:COVER_IMAGE coverImageName:@"" data:nil operatorUserIds:@[user.userId] customType:CUSTOM_TYPE progressHandler:nil  completionHandler:^(SBDOpenChannel * _Nullable channel, SBDError * _Nullable error) {

}];
Swift
SBDOpenChannel.createChannel(withName: NAME, coverImage: COVER_IMAGE, coverImageName: "", data: DATA, operatorUserIds: nil, customType: CUSTOM_TYPE, progressHandler: nil) { (channel, error) in
    // ...
}
  • NAME : the name of the channel, or the Channel Topic.
  • COVER_IMAGE : the file of the cover image, which you can fetch to render into the UI. Alternatively, you can pass a URL of an image by changing coverImage to coverUrl.
  • DATA : a String field to store structured information, such as a JSON String.
  • CUSTOM_TYPE : a String field that allows you to subclassify your channel.

See the Advanced section for more information on cover images and Custom Types.

Getting a list of Open Channels

You can obtain a list of Open Channels by creating a SBDOpenChannelListQuery.
loadNextPage returns a list of SBDOpenChannel objects.

You must be connected to SendBird before requesting an Open Channel List.

Objective-C
SBDOpenChannelListQuery *query = [SBDOpenChannel createOpenChannelListQuery];
[query loadNextPageWithCompletionHandler:^(NSArray<SBDOpenChannel *> * _Nullable channels, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...
}];
Swift
let query = SBDOpenChannel.createOpenChannelListQuery()!
query.loadNextPage(completionHandler: { (channels, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...
})

Getting an Open Channel instance with a URL

Since a channel URL is a unique identifier of an Open Channel, you can use a URL to retrieve a channel instance. It is important to remember that a user must enter the channel before being able to send or receive messages within it.

Store channel URLs to handle lifecycle or state changes in your app. For example, if a user disconnects from SendBird by temporarily switching to another app, you can provide a smooth restoration of the user's state using a stored URL to fetch the appropriate channel instance, then re-entering the user into the channel.

Objective-C
[SBDOpenChannel getChannelWithUrl:channelUrl completionHandler:^(SBDOpenChannel * _Nonnull openChannel, SBDError * _Nullable error) {
    if (error != nil) {
        // Error!
        return;
    }
    // Successfully fetched the channel.
    // Do something with openChannel.
}
}];
Swift
SBDOpenChannel.getWithUrl(channelUrl) { (openChannel, error) in
    if error != nil {
        // Error!
        return
    }
    // Successfully fetched the channel.
    // Do something with openChannel.
}

Entering an Open Channel

A user must enter an Open Channel in order to receive messages.

You can enter up to 10 Open Channels at once.

Entered Open Channels are valid only within the current connection. If you disconnect or reconnect to SendBird, you must re-enter channels in order to continue receiving messages.

Objective-C
[SBDOpenChannel getChannelWithUrl:CHANNEL_URL completionHandler:^(SBDOpenChannel * _Nullable channel, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    [channel enterChannelWithCompletionHandler:^(SBDError * _Nullable error) {
        if (error != nil) {
            NSLog(@"Error: %@", error);
            return;
        }

        // ...
    }];
}];
Swift
SBDOpenChannel.getWithUrl(CHANNEL_URL) { (channel, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    channel?.enter(completionHandler: { (error) in
        if error != nil {
            NSLog("Error: %@", error!)
            return
        }

        // ...
    })
}

Exiting an Open Channel

To stop receiving messages from an Open Channel, you must exit the channel.

Objective-C
[SBDOpenChannel getChannelWithUrl:CHANNEL_URL completionHandler:^(SBDOpenChannel * _Nullable channel, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    [channel exitChannelWithCompletionHandler:^(SBDError * _Nullable error) {
        if (error != nil) {
            NSLog(@"Error: %@", error);
            return;
        }

        // ...
    }];
}];
Swift
SBDOpenChannel.getWithUrl(CHANNEL_URL) { (channel, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    channel?.exitChannel(completionHandler: { (error) in
        if error != nil {
            NSLog("Error: %@", error!)
            return
        }

        // ...
    })
}

Sending messages

Upon entering a channel, a user will be able to send messages of the following types:

  • UserMessage : a User text message.
  • FileMessage : a User binary message.

You can additionally specify a CUSTOM_TYPE to further subclassify a message.

When you send a text message, you can additionally attach arbitrary strings via a DATA field. You can utilize this field to send structured data such as font sizes, font types, or custom JSON objects.

Delivery failures (e.g., due to network issues) will return an exception. By implementing the completionHandler, it is possible to display only the messages that are successfully sent.

Objective-C
[channel sendUserMessage:MESSAGE data:DATA completionHandler:^(SBDUserMessage * _Nullable userMessage, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...
}];
Swift
channel.sendUserMessage(MESSAGE, data: DATA, completionHandler: { (userMessage, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...
})

A user can also send any binary file through SendBird. There are two ways in which you can send a binary file: by sending the file itself, or by sending a URL.

By sending a raw file, you are uploading it to the SendBird servers. Alternatively, you can choose to send a file hosted in your own servers by passing in a URL that points to the file. In this case, your file will not be hosted in the SendBird servers, and downloads of the file will occur through your own servers instead.

Note that if you upload your file directly, a size limit is imposed per file. This limit depends on your plan, and can be viewed from your Dashboard.

No file size limit is imposed if you send a File Message via a URL. Your file will not be uploaded to the SendBird servers.

Objective-C
// Send binary data.
[channel sendFileMessageWithBinaryData:FILE filename:FILE_NAME type:FILE_TYPE size:FILE_SIZE data:CUSTOM_DATA completionHandler:^(SBDFileMessage * _Nonnull fileMessage, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...
}];

// Send URL.
[channel sendFileMessageWithUrl:FILE_URL size:FILE_SIZE type:FILE_TYPE data:CUSTOM_DATA completionHandler:^(SBDFileMessage * _Nonnull fileMessage, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...
}];
Swift
// Send binary data.
channel.sendFileMessage(withBinaryData: FILE, filename: FILE_NAME, type: FILE_TYPE, size: FILE_SIZE, data: CUSTOM_DATA, completionHandler: { (fileMessage, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...    
})

// Send URL.
channel.sendFileMessage(withUrl: FILE_URL, size: FILE_SIZE, type: FILE_TYPE, data: CUSTOM_DATA) { (fileMessage, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...      
}

Receiving messages

Messages can be received by adding a SBDChannelDelegate.
A received BaseMessage object can be of one of three different types of messages.

  • UserMessage : a User text message.
  • FileMessage : a User binary message.
  • AdminMessage : an Admin message which can be sent by an admin through the Platform API.

UNIQUE_HANDLER_ID is a unique identifier to register multiple concurrent handlers.

Objective-C
@interface OpenChannelViewController : ViewController<SBDChannelDelegate>

@end

// ...

[SBDMain addChannelDelegate:self identifier:UNIQUE_HANDLER_ID];

// ...

- (void)channel:(SBDBaseChannel * _Nonnull)sender didReceiveMessage:(SBDBaseMessage * _Nonnull)message {
    // ...
}
Swift
class OpenChannelChattingViewController: UIViewController, SBDChannelDelegate {

    // ...
    SBDMain.add(self as SBDChannelDelegate, identifier: self.delegateIdentifier)

    // ...

    func channel(_ sender: SBDBaseChannel, didReceive message: SBDBaseMessage) {
        // ...
    }
}

You should remove the channel delegate where the UI is no longer valid.

Objective-C
[SBDMain removeChannelDelegateForIdentifier:UNIQUE_HANDLER_ID];
Swift
SBDMain.removeChannelDelegate(forIdentifier: UNIQUE_HANDLER_ID)

Loading previous messages

You can load previous messages by creating a SBDPreviousMessageListQuery instance.
You will be able to display past messages in your UI once they have loaded.

Objective-C
SBDPreviousMessageListQuery *previousMessageQuery = [self.channel createPreviousMessageListQuery];
[previousMessageQuery loadPreviousMessagesWithLimit:30 reverse:YES completionHandler:^(NSArray<SBDBaseMessage *> * _Nullable messages, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }
}];
Swift
let previousMessageQuery = self.channel.createPreviousMessageListQuery()
previousMessageQuery?.loadPreviousMessages(withLimit: 30, reverse: true, completionHandler: { (messages, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...
})

Past messages are queried in fixed numbers (30 in the above code). A new SBDPreviousMessageListQuery instance will load the most recent n messages. Calling loadPreviousMessagesWithLimit:reverse:completionHandler: on the same query instance will load n messages before that. Therefore, you should store your query instance as a member variable in order to traverse through your entire message history.

An important note is that you must receive your first completionHandler callback before invoking loadPreviousMessagesWithLimit:reverse:completionHandler: again.

Loading messages by timestamp

You can retrieve a set number of messages starting from a specific timestamp.

To load messages sent prior to a specifed timestamp, use getPreviousMessagesByTimestamp:limit:reverse:messageType:customType:completionHandler:.

Objective-C
[self.channel getPreviousMessagesByTimestamp:timestamp limit:limit reverse:reverse messageType:SBDMessageTypeFilterAll customType:customType completionHandler:^(NSArray<SBDBaseMessage *> * _Nullable messages, SBDError * _Nullable error) {
    if (error != nil) {
        return;
    }

    // Successfully fetched list of messages sent before timestamp.
}];
Swift
self.channel .getPreviousMessages(byTimestamp: timestamp, limit: limit, reverse: reverse, messageType: SBDMessageTypeFilter.all, customType: nil) { (messages, error) in
    if error != nil {
        return
    }

    // Successfully fetched list of messages sent before timestamp.
}
  • timestamp : The reference timestamp.
  • limit : The number of messages to load. Note that the actual number of results may be larger than the set value when there are multiple messages with the same timestamp as the earliest message.
  • reverse : Whether to reverse the results.
  • messageType : A SBDMessageTypeFilter enum type. Should be one of SBDMessageTypeFilterUser, SBDMessageTypeFilterFile, SBDMessageTypeFilterAdmin, or SBDMessageTypeFilterAll.
  • customType : The Custom Type of the messages to be returned.

To load messages sent after a specified timestamp, call getNextMessagesByTimestamp:limit:reverse:messageType:customType:completionHandler: in a similar fashion. To load results on either side of the reference timestamp, use getPreviousAndNextMessagesByTimestamp:prevLimit:nextLimit:reverse:messageType:customType:completionHandler:.

Getting a list of participants in a channel

Participants are online users who are currently receiving all messages from the Open Channel.

Objective-C
SBDUserListQuery *query = [channel createParticipantListQuery];
[self.query loadNextPageWithCompletionHandler:^(NSArray<SBDUser *> * _Nullable participants, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error");
        return;
    }

    // ...
}];
Swift
let query = openChannel.createParticipantListQuery()
query?.loadNextPage(completionHandler: { (participants, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...     
})

Getting participants' online statuses

To stay updated on each participant's connection status, you must obtain a new SBDUserListQuery, which contains the latest information on each user. To get a SBDUserListQuery for a specific channel, call createOpenChannelListQuery of SBDOpenChannel. If you wish to get the list of all users of your service (application), call createUserListQueryWithUserIds: of SBDMain.

You can then check each of the users' connection statuses by referencing connectionStatus of SBDUser.

If your application needs to keep track of users' connection statuses in real time, we recommend that you receive a new SBDUserListQuery periodically, perhaps in intervals of one minute or more.

connectionStatus can return one of three values:

Objective-C
  • SBDUserConnectionStatusNonAvailable : User's status information cannot be reached.
  • SBDUserConnectionStatusOffline : User is disconnected from SendBird.
  • SBDUserConnectionStatusOnline : User is connected to SendBird.
Swift
  • SBDUserConnectionStatus.nonAvailable : User's status information cannot be reached.
  • SBDUserConnectionStatus.offline : User is disconnected from SendBird.
  • SBDUserConnectionStatus.online : User is connected to SendBird.

Getting a list of banned or muted users in a channel

You can also create a query to get a list of muted or banned users in an Open Channel.

This query is only available for users who are registered as operators of the Open Channel.

Objective-C
SBDUserListQuery *bannedListQuery = [channel createBannedUserListQuery];
[bannedListQuery loadNextPageWithCompletionHandler:^(NSArray<SBDUser *> * _Nullable users, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error");
        return;
    }
}];
SBDUserListQuery *mutedListQuery = [channel createMutedUserListQuery];
[mutedListQuery loadNextPageWithCompletionHandler:^(NSArray<SBDUser *> * _Nullable users, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error");
        return;
    }
}];
Swift
let bannedListQuery = self.channel.createBannedUserListQuery()
bannedListQuery?.loadNextPage(completionHandler: { (users, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...
})
let mutedListQuery = self.channel.createMutedUserListQuery()
mutedListQuery?.loadNextPage(completionHandler: { (users, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...
})

Deleting messages

Users are able to delete messages. An error is returned if a user tries to delete messages sent by someone else.
Channel Operators are able to delete any message in the channel, including those by other users.

Deleting a message fires a MessageDeleted event to all other users in the channel.

Objective-C
[channel deleteMessage:baseMessage completionHandler:^(SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error");
        return;
    }
}];
Swift
self.openChannel.delete(baseMessage) { (error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...
}

You can receive a MessageDeleted event using a Channel Delegate.

Objective-C
@interface OpenChannelViewController : ViewController<SBDChannelDelegate>

@end

- (void)channel:(SBDBaseChannel * _Nonnull)sender messageWasDeleted:(long long)messageId {

}

[SBDMain addChannelDelegate:self identifier:UNIQUE_HANDLER_ID];
Swift
class OpenChannelChattingViewController: UIViewController, SBDChannelDelegate {

    // ...
    SBDMain.add(self as SBDChannelDelegate, identifier: self.delegateIdentifier)

    // ...

    func channel(_ sender: SBDBaseChannel, messageWasDeleted messageId: Int64) {
        // ...
    }
}

Open Channel - Advanced

Admin messages

You can send Admin messages to users in a channel using the SendBird Dashboard or the Platform API.

To do so using the Dashboard, navigate to the Open Channels tab. Inside the message box, you should see an option to send an Admin message. Admin messages should not be longer than 1000 characters.

If you are currently developing under the Free Plan and therefore cannot access the Moderation Tools from the Dashboard, you must send Admin messages through the Platform API.

Channel cover images

When creating a channel, you can add a cover image by specifying an image URL or file.

Objective-C
[SBDOpenChannel createChannelWithName:NAME coverUrl:COVER_URL data:DATA operatorUsers:nil completionHandler:^(SBDOpenChannel * _Nullable channel, SBDError * _Nullable error) {
    if (error != nil) {
        // Error.
        return;
    }
}];
Swift
SBDOpenChannel.createChannel(withName: NAME, coverUrl: COVER_URL, data: DATA, operatorUsers: nil) { (channel, error) in
    if error != nil {
        // Error.
        return
    }
}

You can get the cover image URL using coverUrl. You can also update a channel's cover image by calling updateChannelWithName:.

Custom channel types

When creating a channel, you can additionally specify a Custom Type to further subclassify your channels. This custom type takes on the form of a NSString, and can be handy in searching or filtering channels.

DATA and CUSTOM_TYPE are both String fields that allow you to append information to your channels. The intended use case is for CUSTOM_TYPE to contain information that can subclassify the channel (e.g., distinguishing "School" and "Work" channels). However, both these fields can be flexibly utilized.

Objective-C
[SBDOpenChannel createChannelWithName:NAME coverImage:COVER_IMAGE coverImageName:@"" data:nil operatorUserIds:@[user.userId] customType:CUSTOM_TYPE progressHandler:nil  completionHandler:^(SBDOpenChannel * _Nullable channel, SBDError * _Nullable error) {
    if (error != nil) {
        // Error
        return;
    }
}
}];
Swift
SBDOpenChannel.createChannel(withName: NAME, coverImage: COVER_IMAGE, coverImageName: "", data: DATA, operatorUserIds: nil, customType: CUSTOM_TYPE, progressHandler: nil) { (channel, error) in
    if error != nil {
        // Error.
        return
    }    
}

To get a channel's Custom Type, read channel.customType.

Custom message types

Likewise, you can specify a Custom Type for messages in order to categorize them into more specific groups. This custom type takes on the form of a NSString, and can be useful in searching or filtering messages.

DATA and CUSTOM_TYPE are both String fields that allow you to append information to your messages. The intended use case is for CUSTOM_TYPE to contain information that can subclassify the message (e.g., distinguishing "FILE_IMAGE" and "FILE_AUDIO" type messages). However, both these fields can be flexibly utilized.

To embed a Custom Type into a message, simply pass a String parameter to sendUserMessage: or sendFileMessage:.

Objective-C
[channel sendUserMessage:MESSAGE data:DATA customType:CUSTOM_TYPE completionHandler:^(SBDUserMessage * _Nullable userMessage, SBDError * _Nullable error) {
    if (error != nil) {
        // Error
        return;
    }
}];
Swift
channel?.sendUserMessage(MESSAGE, data: DATA, customType: CUSTOM_TYPE, completionHandler: { (message, error) in
    if error != nil {
        // Error.
        return
    }    
})

To get a message's Custom Type, read message.customType.

Message auto-translation

This feature is not available under the Free plan. Contact sales@sendbird.com if you wish to implement this functionality.

SendBird makes it possible for messages to be sent in different languages through its auto-translation feature.
Pass in a NSArray of language codes to sendUserMessage: to request translated messages in the corresponding languages.

Objective-C
[channel sendUserMessage:MESSAGE data:DATA customType:CUSTOM_TYPE targetLanguages:@[@"es", @"ko"] completionHandler:^(SBDUserMessage * _Nullable userMessage, SBDError * _Nullable error) {
    if (error != nil) {
        // Error
        return;
    }
}];
Swift
channel?.sendUserMessage(MESSAGE, data: DATA, customType: CUSTOM_TYPE, targetLanguages: ["es", "ko"], completionHandler: { (message, error) in
    if error != nil {
        // Error.
        return
    }      
})

You can obtain translations of a message using userMessage.translations. This method returns a NSArray containing the language codes and translations.

Objective-C
- (void)channel:(SBDBaseChannel * _Nonnull)sender didReceiveMessage:(SBDBaseMessage * _Nonnull)message {
    NSArray *translations = ((SBDUserMessage *)message).translations;
    NSString *esTranslation = translations[@"es"];

    // Display translation in UI.
}
Swift
func channel(_ sender: SBDBaseChannel, didReceive message: SBDBaseMessage) {
    let translations = (message as! SBDUserMessage).translations
    let esTranslation = translations["es"]

    // Display translation in UI.
}

The message auto-translation supports 53 languages. For the language codes table, see the Miscellaneous > Supported Languages.

You can search for specific channels by adding a keyword to your SBDOpenChannelListQuery.
There are two types of keywords: a Name Keyword and a URL Keyword.

Adding a Name Keyword to a query will return the list of Open Channels that have the keyword included in their names.

Objective-C
SBDOpenChannelListQuery *query = [SBDOpenChannel createOpenChannelListQuery];
[query setNameKeyword:@"NameKeyword"];
[query loadNextPageWithCompletionHandler:^(NSArray<SBDOpenChannel *> * _Nullable channels, SBDError * _Nullable error) {
    if (error != nil) {
        return;
    }

    // Returns a List of channels that have "NameKeyword" in their names.
}];
Swift
let query = SBDOpenChannel.createOpenChannelListQuery()
query?.nameKeyword = "NameKeyword"
query?.loadNextPage(completionHandler: { (channels, error) in
    if error != nil {
        return
    }

    // Returns a List of channels that have "NameKeyword" in their names.
}

Adding a URL Keyword to a query will return the Open Channel whose URL exactly matches the given keyword.

Objective-C
SBDOpenChannelListQuery *query = [SBDOpenChannel createOpenChannelListQuery];
[query setUrlKeyword:@"UrlKeyword"];
[query loadNextPageWithCompletionHandler:^(NSArray<SBDOpenChannel *> * _Nullable channels, SBDError * _Nullable error) {
    if (error != nil) {
        return;
    }

    // Returns a List containing a single channel with the URL that matches the URL Keyword.
}];
Swift
let query = SBDOpenChannel.createOpenChannelListQuery()
query?.urlKeyword = "UrlKeyword"
query?.loadNextPage(completionHandler: { (channels, error) in
    if error != nil {
        return
    }

    // Returns a List containing a single channel with the URL that matches the URL Keyword.
}

File Message thumbnails

This feature is not available under the Free plan. Contact sales@sendbird.com if you wish to implement this functionality.

When sending an image file, you can choose to create thumbnails of the image, which you can fetch and render into your UI. You can specify up to 3 different dimensions to generate thumbnail images in, which can be convenient for supporting various display densities.

Supported file types are files whose MIME type is image/* or video/*.

The SDK does not support creating thumbnails when sending a File Message via a file URL.

Create a NSArray of SBDThumbnailSize objects to pass to [channel sendFileMessageWithBinaryData:filename:type:size:thumbnailSizes:data:customType:progressHandler:completionHandler:]. A SBDThumbnailSize can be created with the constructor [SBDThumbnailSize makeWithMaxCGSize:] or [SBDThumbnailSize makeWithMaxWidth:maxHeight:], where the values specify pixels. The completionHandler callback will subsequently return a NSArray of SBDThumbnail objects that each contain the URL of the generated thumbnail image file.

Objective-C
NSMutableArray<SBDThumbnailSize *> *thumbnailSizes = [[NSMutableArray alloc] init];
[thumbnailSizes addObject:[SBDThumbnailSize makeWithMaxCGSize:CGSizeMake(100.0, 100.0)]];
[thumbnailSizes addObject:[SBDThumbnailSize makeWithMaxWidth:200.0 maxHeight:200.0]];

[channel sendFileMessageWithBinaryData:file filename:name type:type size:size thumbnailSizes:thumbnailSizes data:data customType:customType progressHandler:nil completionHandler:^(SBDFileMessage * _Nullable fileMessage, SBDError * _Nullable error) {
    if (error != nil) {
        // Error!
        return;
    }

    SBDThumbnail *first = fileMessage.thumbnails[0];
    SBDThumbnail *second = fileMessage.thumbnails[1];

    CGSize maxSizeFirst = first.maxSize;
    CGSize maxSizeSecond = second.maxSize;

    NSString *urlFirst = first.url;
    NSString *urlSecond = second.url;
}];
Swift
var thumbnailSizes = [SBDThumbnailSize]()

thumbnailSizes.append(SBDThumbnailSize.make(withMaxCGSize: CGSize(width: 100.0, height: 100.0))!)
thumbnailSizes.append(SBDThumbnailSize.make(withMaxWidth: 200.0, maxHeight: 200.0)!)

channel.sendFileMessage(withBinaryData: file, filename: name, type: type, size: size, thumbnailSizes: thumbnailSizes, data: data, customType: customType, progressHandler: nil) { (fileMessage, error) in
    if error != nil {
        // Error!
        return
    }

    let first = fileMessage?.thumbnails?[0]
    let second = fileMessage?.thumbnails?[1]

    let maxSizeFirst = first?.maxSize
    let maxSizeSecond = second?.maxSize

    let urlFirst = first?.url
    let urlSecond = second?.url
}

maxWidth and maxHeight specify the maximum dimensions of the thumbnail. Your image will be scaled down evenly to fit within the bounds of (maxWidth, maxHeight). Note that if the original image is smaller than the specified dimensions, the thumbnail will not be scaled. url returns the location of the generated thumbnail file within the SendBird servers.

Group Channel

A Group Channel is a private chat. A user may join the chat only through an invitation by another user who is already a member of the chatroom. A Group Channel can consist of one to hundreds of members. Creating a channel with two members allows 1-to-1 messaging.

A user will automatically receive all messages from the group channels that they are a member of.

Creating a Group Channel

A Group Channel can be created on demand by a user through the SendBird SDK.

Distinct property : A channel with the Distinct property enabled will always be reused for the same members. If a new member is invited, or if a member leaves the channel, then the distinct property is disabled automatically. For example, in the case that a Group Channel with 3 members, A, B, and C, already exists, attempting to create a new channel with the same members will just return a reference to the existing channel.

Consequently, we recommend that you enable the Distinct property in 1-to-1 messaging channels in order to reuse the same channel when a user chooses to directly message a friend. If the property is disabled, the user will create a new channel even if they have had previous conversations with the friend, and therefore will not be able to see or access previously sent messages or data.

Objective-C
[SBDGroupChannel createChannelWithUserIds:userIds isDistinct:NO completionHandler:^(SBDGroupChannel * _Nullable channel, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...
}];
Swift
SBDGroupChannel.createChannel(withUserIds: userIds, isDistinct: false) { (channel, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...    
}

You can also append information by passing additional arguments.

Objective-C
[SBDGroupChannel createChannelWithName:NAME isDistinct:IS_DISTINCT userIds:USER_IDS coverUrl:COVER_IMAGE_URL data:DATA customType:CUSTOM_TYPE completionHandler:^(SBDGroupChannel * _Nullable channel, SBDError * _Nullable error) {

}];
Swift
SBDGroupChannel.createChannel(withName: NAME, isDistinct: IS_DISTINCT, userIds: USER_IDS, coverUrl: COVER_IMAGE_URL, data: DATA, customType: CUSTOM_TYPE) { (channel, error) in

}
  • NAME : the name of the channel, or the Channel Topic.
  • COVER_IMAGE_URL : the URL of the cover image, which you can fetch to render into the UI. Alternatively, you can pass in an image file by changing coverUrl to coverImage.
  • DATA : a String field to store structured information, such as a JSON String.
  • CUSTOM_TYPE : a String field that allows you to subclassify your channel.

See the Advanced section for more information on cover images and Custom Types.

You can also create channels via the SendBird Platform API.
You should utilize the Platform API when you wish to control channel creations and member invitations on the server-side.

Getting a Group Channel instance with a URL

Since a channel URL is a unique identifier of a Group Channel, you can use a URL to retrieve a channel instance.

Store channel URLs to handle lifecycle or state changes in your app. For example, if a user disconnects from SendBird by temporarily switching to another app, you can provide a smooth restoration of the user's state using a stored URL to fetch the appropriate channel instance, then re-entering the user into the channel.

Objective-C
[SBDGroupChannel getChannelWithUrl:channelUrl completionHandler:^(SBDGroupChannel * _Nonnull groupChannel, SBDError * _Nullable error) {
    if (error != nil) {
        // Error!
        return;
    }

    // Successfully fetched the channel.
    // Do something with groupChannel.
}
}];
Swift
SBDGroupChannel.getWithUrl(channelUrl) { (groupChannel, error) in
    if error != nil {
        // Error!
        return
    }

    // Successfully fetched the channel.
    // Do something with groupChannel.
}

Inviting users to an existing channel

Only members of the channel are able to invite new users into the channel.

You can choose whether the newly invited user is able to access past messages in the channel. In your Dashboard Settings - Messages section, there is an option to show channel history. If this option is enabled, new users will be able to view all messages sent before they have joined the channel. If not, new users will only be able to see messages sent after they had been invited.

Show channel history is enabled by default.

Objective-C
[channel inviteUserIds:userIds completionHandler:^(SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...
}];
Swift
channel.inviteUserIds(userIds) { (error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...
}

Leaving a Group Channel

Users will no longer receive messages from channels they have left.

Objective-C
[channel leaveChannelWithCompletionHandler:^(SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...
}];
Swift
channel.leave { (error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...    
}

Getting a list of my Group Channels

You can obtain a list of Group Channels by creating a query with createMyGroupChannelListQuery of SBDGroupChannel.
loadNextPage returns a list of SBDGroupChannel objects.

You can also set an option to include empty channels with includeEmptyChannel. Empty channels are channels that have been created but contain no sent messages. By default, empty channels are not shown.

Objective-C
SBDGroupChannelListQuery *query = [SBDGroupChannel createMyGroupChannelListQuery];
query.includeEmptyChannel = YES; // Include empty group channels.
[query loadNextPageWithCompletionHandler:^(NSArray<SBDGroupChannel *> * _Nullable channels, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...
}];
Swift
let query = SBDGroupChannel.createMyGroupChannelListQuery()!
query?.includeEmptyChannel = true
query?.loadNextPage(completionHandler: { (channels, error) in
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...    
})

Querying Group Channels by User IDs

It is possible to filter a channel search by user IDs. This can be done by calling setUserIdsExactFilter: or setUserIdsIncludeFilter:queryType: of SBDGroupChannelListQuery.

Given an example where a user (with the ID "User") is part of two Group Channels:

  • channelA: { "User", "John", "Jay" }
  • channelB: { "User", "John", "Jay", "Jin" }

An ExactFilter returns the list of channels containing exactly the queried userIDs.

Objective-C
SBDGroupChannelListQuery *filteredQuery = [SBDGroupChannel createMyGroupChannelListQuery];
[filteredQuery setUserIdsExactFilter:@[@"John", @"Jay"]];

[query2 loadNextPageWithCompletionHandler:^(NSArray<SBDGroupChannel *> * _Nullable channels, SBDError * _Nullable error) {
    // returns channelA only.
}];
Swift
let query = SBDGroupChannel.createMyGroupChannelListQuery()
query?.setUserIdsExactFilter(["John", "Jay"])
query?.loadNextPage(completionHandler: { (channels, error) in
    // returns channelA only.
})

An IncludeFilter returns channels where the userIDs are included. This method can return one of two different results, based on the parameter queryType.

Objective-C
SBDGroupChannelListQuery *filteredQuery = [SBDGroupChannel createMyGroupChannelListQuery];
[filteredQuery setUserIdsIncludeFilter:@[@"John", @"Jay", @"Jin"] queryType:SBDGroupChannelListQueryTypeAnd];
[filteredQuery loadNextPageWithCompletionHandler:^(NSArray<SBDGroupChannel *> * _Nullable channels, SBDError * _Nullable error) {
    // returns channels that include the ids { John, Jay, Jin } as a subset.

    // i.e. returns channelB only.
}];

[filteredQuery setUserIdsIncludeFilter:@[@"John", @"Jay", @"Jin"] queryType:SBDGroupChannelListQueryTypeOr];
[filteredQuery loadNextPageWithCompletionHandler:^(NSArray<SBDGroupChannel *> * _Nullable channels, SBDError * _Nullable error) {
    // returns channels that include { John }, plus channels that include { Jay }, 
    // plus channels that include { Jin }.

    // i.e. returns both channelA, channelB.
}];
Swift
let query = SBDGroupChannel.createMyGroupChannelListQuery()
query?.setUserIdsIncludeFilter(["John", "Jay", "Jin"], queryType: SBDGroupChannelListQueryType.and)
query?.loadNextPage(completionHandler: { (channels, error) in
    // returns channels that include the ids { John, Jay, Jin } as a subset.

    // i.e. returns channelB only.
})

query?.setUserIdsIncludeFilter(["John", "Jay", "Jin"], queryType: SBDGroupChannelListQueryType.or)
query?.loadNextPage(completionHandler: { (channels, error) in
    // returns channels that include { John }, plus channels that include { Jay }, 
    // plus channels that include { Jin }.

    // i.e. returns both channelA, channelB.
})

Sending messages

Upon entering a channel, a user will be able to send messages of the following types:

  • UserMessage : a User text message.
  • FileMessage : a User binary message.

You can additionally specify a CUSTOM_TYPE to further subclassify a message.

When you send a text message, you can additionally attach arbitrary strings via a data field. You can utilize this field to send structured data such as font size, font type, or a custom JSON object.

Delivery failures (e.g., due to network issues) will return an exception. By implementing the completionHandler, it is possible to display only the messages that are successfully sent.

Objective-C
[channel sendUserMessage:MESSAGE data:DATA completionHandler:^(SBDUserMessage * _Nullable userMessage, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...
}];
Swift
channel.sendUserMessage(MESSAGE, data: DATA, completionHandler: { (userMessage, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...
})

A user can also send any binary file through SendBird. There are two ways in which you can send a binary file: by sending the file itself, or by sending a URL.

By sending a raw file, you are uploading it to the SendBird servers. Alternatively, you can choose to send a file hosted in your own servers by passing in a URL that points to the file. In this case, your file will not be hosted in the SendBird servers, and downloads of the file will occur through your own servers instead.

Note that if you upload your file directly, a size limit is imposed per file. This limit depends on your plan, and can be viewed from your Dashboard.

No file size limit is imposed if you send a File Message via a URL. Your file will not be uploaded to the SendBird servers.

Objective-C
// Send binary data.
[channel sendFileMessageWithBinaryData:FILE filename:FILE_NAME type:FILE_TYPE size:FILE_SIZE data:CUSTOM_DATA completionHandler:^(SBDFileMessage * _Nonnull fileMessage, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...
}];

// Send URL.
[channel sendFileMessageWithUrl:FILE_URL size:FILE_SIZE type:FILE_TYPE data:CUSTOM_DATA completionHandler:^(SBDFileMessage * _Nonnull fileMessage, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...
}];
Swift
// Send binary data.
channel.sendFileMessage(withBinaryData: FILE, filename: FILE_NAME, type: FILE_TYPE, size: FILE_SIZE, data: CUSTOM_DATA, completionHandler: { (fileMessage, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...    
})

// Send URL.
channel.sendFileMessage(withUrl: FILE_URL, size: FILE_SIZE, type: FILE_TYPE, data: CUSTOM_DATA) { (fileMessage, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...      
}

Receiving messages

Messages can be received by adding a ChannelHandler. A received BaseMessage object can be of one of three different types of messages.

  • UserMessage : a User text message.
  • FileMessage : a User binary message.
  • AdminMessage : an Admin message which can be sent by an admin through the Platform API.

UNIQUE_HANDLER_ID is a unique identifier to register multiple concurrent handlers.

Objective-C
@interface GroupChannelViewController : ViewController<SBDChannelDelegate>

@end

// ...

[SBDMain addChannelDelegate:self identifier:UNIQUE_HANDLER_ID];

// ...

- (void)channel:(SBDBaseChannel * _Nonnull)sender didReceiveMessage:(SBDBaseMessage * _Nonnull)message {
    // ...
}
Swift
class GroupChannelChattingViewController: UIViewController, SBDChannelDelegate {

    // ...
    SBDMain.add(self as SBDChannelDelegate, identifier: UNIQUE_HANDLER_ID)

    // ...

    func channel(_ sender: SBDBaseChannel, didReceive message: SBDBaseMessage) {
        // ...
    }
}

You should remove the channel delegate where the UI is no longer valid.

Objective-C
[SBDMain removeChannelDelegateForIdentifier:UNIQUE_HANDLER_ID];
Swift
SBDMain.removeChannelDelegate(forIdentifier: UNIQUE_HANDLER_ID)

Loading previous messages

You can load previous messages by creating a SBDPreviousMessageListQuery instance. You will be able to display past messages in your UI once they have loaded.

Whether a user can load messages prior to joining the channel depends on your settings. In your Dashboard Settings - Messages section, there is an option to show channel history. If this option is enabled, new users will be able to view all messages sent before they have joined the channel. If not, new users will only be able to see messages sent after they had been invited.

Objective-C
SBDPreviousMessageListQuery *previousMessageQuery = [groupChannel createPreviousMessageListQuery];
[self.previousMessageQuery loadPreviousMessagesWithLimit:30 reverse:YES completionHandler:^(NSArray<SBDBaseMessage *> * _Nullable messages, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error");
        return;
    }
}];
Swift
let messageQuery = self.groupChannel.createPreviousMessageListQuery()
messageQuery?.loadPreviousMessages(withLimit: 30, reverse: true, completionHandler: { (messages, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...     
})

Past messages are queried in fixed numbers (30 in the above code). A new SBDPreviousMessageListQuery instance will load the most recent n messages. Calling loadPreviousMessagesWithLimit:reverse:completionHandler: on the same query instance will load n messages before that. Therefore, you should store your query instance as a member variable in order to traverse through your entire message history.

An important note is that you must receive your first completionHandler callback before invoking loadPreviousMessagesWithLimit:reverse:completionHandler: again.

Loading messages by timestamp

You can retrieve a set number of messages starting from a specific timestamp.

To load messages sent prior to a specifed timestamp, use getPreviousMessagesByTimestamp:limit:reverse:messageType:customType:completionHandler:.

Objective-C
[self.channel getPreviousMessagesByTimestamp:timestamp limit:limit reverse:reverse messageType:SBDMessageTypeFilterAll customType:customType completionHandler:^(NSArray<SBDBaseMessage *> * _Nullable messages, SBDError * _Nullable error) {
    if (error != nil) {
        return;
    }

    // Successfully fetched list of messages sent before timestamp.
}];
Swift
self.channel .getPreviousMessages(byTimestamp: timestamp, limit: limit, reverse: reverse, messageType: SBDMessageTypeFilter.all, customType: nil) { (messages, error) in
    if error != nil {
        return
    }

    // Successfully fetched list of messages sent before timestamp.
}
  • timestamp : The reference timestamp.
  • limit : The number of messages to load. Note that the actual number of results may be larger than the set value when there are multiple messages with the same timestamp as the earliest message.
  • reverse : Whether to reverse the results.
  • messageType : A SBDMessageTypeFilter enum type. Should be one of SBDMessageTypeFilterUser, SBDMessageTypeFilterFile, SBDMessageTypeFilterAdmin, or SBDMessageTypeFilterAll.
  • customType : The Custom Type of the messages to be returned.

To load messages sent after a specified timestamp, call getNextMessagesByTimestamp:limit:reverse:messageType:customType:completionHandler: in a similar fashion. To load results on either side of the reference timestamp, use getPreviousAndNextMessagesByTimestamp:prevLimit:nextLimit:reverse:messageType:customType:completionHandler:.

Deleting messages

Users are able to delete messages. An error is returned if a user tries to delete messages sent by someone else.
Channel Operators are able to delete any message in the channel, including those by other users.

Deleting a message fires a MessageDeleted event to all other users in the channel.

Objective-C
[channel deleteMessage:baseMessage completionHandler:^(SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error");
        return;
    }

    // ...
}];
Swift
self.groupChannel.delete(baseMessage, completionHandler: { (error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...
})

You can receive a MessageDeleted event using a Channel Delegate.

Objective-C
@interface GroupChannelViewController : ViewController<SBDChannelDelegate>

@end

// ...

[SBDMain addChannelDelegate:self identifier:UNIQUE_HANDLER_ID];

// ...

- (void)channel:(SBDBaseChannel * _Nonnull)sender messageWasDeleted:(long long)messageId {

}
Swift
class GroupChannelChattingViewController: UIViewController, SBDChannelDelegate {

    // ...

    SBDMain.add(self as SBDChannelDelegate, identifier: UNIQUE_HANDLER_ID)

    // ...

    func channel(_ sender: SBDBaseChannel, messageWasDeleted messageId: Int64) {

    }
}

1-to-1 Chat

A 1-to-1 chat is just a Group Channel with two members.

Creating a 1-to-1 chat

A Group Channel can be created on demand by a user through the client SDK. Pass in two user IDs to create a 1-to-1 chat between two users.

You would typically want a 1-to-1 chat to be Distinct. If the Distinct property is not enabled, the user will be able to create a new channel with the same opponent, even if they have had previous conversations. In this case, multiple 1-to-1 chats between the same two users might exist, each with its own chat history and data.

Objective-C
[SBDGroupChannel createChannelWithUserIds:userIds isDistinct:YES completionHandler:^(SBDGroupChannel * _Nullable channel, SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error: %@", error);
        return;
    }

    // ...
}];
Swift
SBDGroupChannel.createChannel(withUserIds: userIds, isDistinct: true) { (channel, error) in
    if error != nil {
        NSLog("Error: %@", error!)
        return
    }

    // ...    
}

You can also append information by passing additional arguments.

Objective-C
[SBDGroupChannel createChannelWithName:NAME isDistinct:IS_DISTINCT userIds:USER_IDS coverUrl:COVER_IMAGE_URL data:DATA customType:CUSTOM_TYPE completionHandler:^(SBDGroupChannel * _Nullable channel, SBDError * _Nullable error) {

}];
Swift
SBDGroupChannel.createChannel(withName: NAME, isDistinct: IS_DISTINCT, userIds: USER_IDS, coverUrl: COVER_IMAGE_URL, data: DATA, customType: CUSTOM_TYPE) { (channel, error) in

}
  • NAME : the name of the channel, or the Channel Topic.
  • COVER_IMAGE_URL : the URL of the cover image, which you can fetch to render into the UI. Alternatively, you can pass in an image file by changing coverUrl to coverImage.
  • DATA : a String field to store structured information, such as a JSON String.
  • CUSTOM_TYPE : a String field that allows you to subclassify your channel.

See the Advanced section for more information on cover images and Custom Types.

You can also create channels via the SendBird Platform API. You should utilize the Platform API when you wish to control channel creations and member invitations on the server-side.

Group Channel - Advanced

Getting a list of all channel members

You can obtain a list of members in a Group Channel by referencing the members attribute within SBDGroupChannel.

Objective-C
NSArray *members = groupChannel.members;
Swift
let members = groupChannel.members;

Members are automatically updated when you are online. If you disconnect from SendBird and reconnect, you should refresh the channel to be updated with the latest information.

Objective-C
[channel refreshWithCompletionHandler:^(SBDError * _Nullable error) {
    if (error != nil) {
        return;
    }

    // The channel object has been refreshed.
}];
Swift
channel.refresh { (error) in
    if error != nil {
        return
    }

    // The channel object has been refreshed.
}

Getting participants' online statuses

To stay updated on each participant's connection status, you must obtain a new SBDUserListQuery, which contains the lastest information on each user. To get a SBDUserListQuery for a specific channel, call createOpenChannelListQuery of SBDOpenChannel. If you wish to get the list of all users of your service (application), call createUserListQueryWithUserIds: of SBDMain.

You can then check each of the users' connection statuses by referencing connectionStatus of SBDUser.

If your application needs to keep track of users' connection statuses in real time, we recommend that you receive a new SBDUserListQuery periodically, perhaps in intervals of one minute or more.

connectionStatus can return one of three values:

Objective-C
  • SBDUserConnectionStatusNonAvailable : User's status information cannot be reached.
  • SBDUserConnectionStatusOffline : User is disconnected from SendBird.
  • SBDUserConnectionStatusOnline : User is connected to SendBird.
Swift
  • SBDUserConnectionStatus.nonAvailable : User's status information cannot be reached.
  • SBDUserConnectionStatus.offline : User is disconnected from SendBird.
  • SBDUserConnectionStatus.online : User is connected to SendBird.

Typing indicators

You can send typing events by invoking startTyping and endTyping.

Objective-C
[groupChannel startTyping];
[groupChannel endTyping];
Swift
self.groupChannel.startTyping()
self.groupChannel.endTyping()

You can receive a TypingStatusUpdate event with a Channel Delegate.

Objective-C
@interface GroupChannelViewController : ViewController<SBDChannelDelegate>

@end

// ...

[SBDMain addChannelDelegate:self identifier:UNIQUE_HANDLER_ID];

// ...

- (void)channelDidUpdateTypingStatus:(SBDGroupChannel * _Nonnull)sender {
    if ([sender.channelUrl isEqualToString:self.channel.channelUrl]) {
        NSArray<SBDUser *> *members = [sender getTypingMembers];
        // Refresh typing status.
    }
}
Swift
class GroupChannelChattingViewController: UIViewController, SBDChannelDelegate {

    // ...

    SBDMain.add(self as SBDChannelDelegate, identifier: UNIQUE_HANDLER_ID)

    // ...

    func channelDidUpdateTypingStatus(_ sender: SBDGroupChannel) {
        if sender.channelUrl == self.groupChannel.channelUrl {
            let members = sender.getTypingMembers()

            // Refresh typing status.
        }
    }
}

Read Receipts

A user can indicate that they have read a message by calling markAsRead.

Objective-C
[groupChannel markAsRead];
Swift
groupChannel.markAsRead()

This broadcasts a ReadReceiptUpdate event, which can be handled with a channel delegate.

Objective-C
@interface GroupChannelViewController : ViewController<SBDChannelDelegate>

@end

// ...

[SBDMain addChannelDelegate:self identifier:UNIQUE_HANDLER_ID];

// ...

- (void)channelDidUpdateReadReceipt:(SBDGroupChannel * _Nonnull)sender {
    // Refresh messages
}
Swift
class GroupChannelChattingViewController: UIViewController, SBDChannelDelegate {

    // ...

    SBDMain.add(self as SBDChannelDelegate, identifier: UNIQUE_HANDLER_ID)

    // ...

    func channelDidUpdateReadReceipt(_ sender: SBDGroupChannel) {
        // Refresh messages
    }
}

getReadReceiptOfMessage returns the number of members in the channel who have not read the message.

Objective-C
int unreadCount = [channel getReadReceiptOfMessage:msg];
Swift
let unreadCount = self.groupChannel.getReadReceipt(of: msg)

Viewing who has read a message

You can view who has read a message with getReadMembersWithMessage:. This list is updated when the message's read receipt is updated. Therefore, you should replace your previous message instance with the newly received message in channelDidUpdateReadReceipt: for real-time updates.

Objective-C
NSArray *readMembers = [self.channel getReadMembersWithMessage:message];
Swift
let readMembers = self.groupChannel.getReadMembers(with: message)

Similarly, you can also view who has not read the message with getUnreadMembersWithMessage:.

Admin messages

You can send Admin messages to users in a channel using the SendBird Dashboard or the Platform API.

To do so using the Dashboard, navigate to the Group Channels tab. Inside the message box, you should see an option to send an Admin message. Admin messages should not be longer than 1000 characters.

If you are currently developing under the Free Plan and therefore cannot access the Moderation Tools from the Dashboard, you must send Admin messages through the Platform API.

Channel cover images

When creating a channel, you can add a cover image by specifying an image URL or file.

Objective-C
[SBDGroupChannel createChannelWithName:NAME isDistinct:IS_DISTINCT users:USERS coverUrl:COVER_URL data:DATA completionHandler:^(SBDGroupChannel * _Nullable channel, SBDError * _Nullable error) {
    if (error != nil) {
        // Error.
        return;
    }
}];
Swift
SBDGroupChannel.createChannel(withName: NAME, users: USERS, coverUrl: COVER_URL, data: DATA) { (channel, error) in
    if error != nil {
        // Error.
        return
    }
}

You can get the cover image URL referencing coverUrl. You can also update a channel's cover image by calling updateChannelWithName:.

Custom channel types

When creating a channel, you can additionally specify a Custom Type to further subclassify your channels. This custom type takes on the form of a NSString, and can be handy in searching or filtering channels.

DATA and CUSTOM_TYPE are both String fields that allow you to append information to your channels. The intended use case is for CUSTOM_TYPE to contain information that can subclassify the channel (e.g., distinguishing "School" and "Work" channels). However, both these fields can be flexibly utilized.

Objective-C
[SBDGroupChannel createChannelWithName:NAME isDistinct:IS_DISTINCT userIds:USER_IDS coverImage:COVER_IMAGE coverImageName:COVER_IMAGE_NAME data:DATA customType:CUSTOM_TYPE progressHandler:nil completionHandler:^(SBDGroupChannel * _Nullable channel, SBDError * _Nullable error) {
    if (error != nil) {
        // Error
        return;
    }
}
}];
Swift
SBDGroupChannel.createChannel(withName: NAME, isDistinct: IS_DISTINCT, userIds: USER_IDS, coverImage: COVER_IMAGE, coverImageName: COVER_IMAGE_NAME, data: DATA, customType: CUSTOM_TYPE, progressHandler: nil) { (channel, error) in
    if error != nil {
        // Error.
        return
    }    
}

To get a channel's Custom Type, read channel.customType.

Custom message types

Likewise, you can specify a Custom Type for messages in order to categorize them into more specific groups. This custom type takes on the form of a NSString, and can be useful in searching or filtering messages.

DATA and CUSTOM_TYPE are both String fields that allow you to append information to your messages. The intended use case is for CUSTOM_TYPE to contain information that can subclassify the message (e.g., distinguishing "FILE_IMAGE" and "FILE_AUDIO" type messages). However, both these fields can be flexibly utilized.

To embed a Custom Type into a message, simply pass a String parameter to sendUserMessage: or sendFileMessage:.

Objective-C
[channel sendUserMessage:MESSAGE data:DATA customType:CUSTOM_TYPE completionHandler:^(SBDUserMessage * _Nullable userMessage, SBDError * _Nullable error) {
    if (error != nil) {
        // Error
        return;
    }
}];
Swift
channel?.sendUserMessage(MESSAGE, data: DATA, customType: CUSTOM_TYPE, completionHandler: { (message, error) in
    if error != nil {
        // Error.
        return
    }    
})

To get a message's Custom Type, read message.customType.

Message auto-translation

This feature is not available under the Free plan. Contact sales@sendbird.com if you wish to implement this functionality.

SendBird makes it possible for messages to be sent in different languages through its auto-translation feature.
Pass in a NSArray of language codes to sendUserMessage: to request translated messages in the corresponding languages.

Objective-C
[channel sendUserMessage:MESSAGE data:DATA customType:CUSTOM_TYPE targetLanguages:@[@"es", @"ko"] completionHandler:^(SBDUserMessage * _Nullable userMessage, SBDError * _Nullable error) {
    if (error != nil) {
        // Error
        return;
    }
}];
Swift
channel?.sendUserMessage(MESSAGE, data: DATA, customType: CUSTOM_TYPE, targetLanguages: ["es", "ko"], completionHandler: { (message, error) in
    if error != nil {
        // Error.
        return
    }      
})

You can obtain translations of a message using userMessage.translations. This method returns a NSArray containing the language codes and translations.

Objective-C
- (void)channel:(SBDBaseChannel * _Nonnull)sender didReceiveMessage:(SBDBaseMessage * _Nonnull)message {
    NSArray *translations = ((SBDUserMessage *)message).translations;
    NSString *esTranslation = translations[@"es"];

    // Display translation in UI.
}
Swift
func channel(_ sender: SBDBaseChannel, didReceive message: SBDBaseMessage) {
    let translations = (message as! SBDUserMessage).translations
    let esTranslation = translations["es"]

    // Display translation in UI.
}

The message auto-translation supports 53 languages. For the language codes table, see the Miscellaneous > Supported Languages.

File Message thumbnails

This feature is not available under the Free plan. Contact sales@sendbird.com if you wish to implement this functionality.

When sending an image file, you can choose to create thumbnails of the image, which you can fetch and render into your UI. You can specify up to 3 different dimensions to generate thumbnail images in, which can be convenient for supporting various display densities.

Supported file types are files whose MIME type is image/* or video/*.

The SDK does not support creating thumbnails when sending a File Message via a file URL.

Create a NSArray of SBDThumbnailSize objects to pass to [channel sendFileMessageWithBinaryData:filename:type:size:thumbnailSizes:data:customType:progressHandler:completionHandler:]. A SBDThumbnailSize can be created with the constructor [SBDThumbnailSize makeWithMaxCGSize:] or [SBDThumbnailSize makeWithMaxWidth:maxHeight:], where the values specify pixels. The completionHandler callback will subsequently return a NSArray of SBDThumbnail objects that each contain the URL of the generated thumbnail image file.

Objective-C
NSMutableArray<SBDThumbnailSize *> *thumbnailSizes = [[NSMutableArray alloc] init];
[thumbnailSizes addObject:[SBDThumbnailSize makeWithMaxCGSize:CGSizeMake(100.0, 100.0)]];
[thumbnailSizes addObject:[SBDThumbnailSize makeWithMaxWidth:200.0 maxHeight:200.0]];

[channel sendFileMessageWithBinaryData:file filename:name type:type size:size thumbnailSizes:thumbnailSizes data:data customType:customType progressHandler:nil completionHandler:^(SBDFileMessage * _Nullable fileMessage, SBDError * _Nullable error) {
    if (error != nil) {
        // Error!
        return;
    }

    SBDThumbnail *first = fileMessage.thumbnails[0];
    SBDThumbnail *second = fileMessage.thumbnails[1];

    CGSize maxSizeFirst = first.maxSize;
    CGSize maxSizeSecond = second.maxSize;

    NSString *urlFirst = first.url;
    NSString *urlSecond = second.url;
}];
Swift
var thumbnailSizes = [SBDThumbnailSize]()

thumbnailSizes.append(SBDThumbnailSize.make(withMaxCGSize: CGSize(width: 100.0, height: 100.0))!)
thumbnailSizes.append(SBDThumbnailSize.make(withMaxWidth: 200.0, maxHeight: 200.0)!)

channel.sendFileMessage(withBinaryData: file, filename: name, type: type, size: size, thumbnailSizes: thumbnailSizes, data: data, customType: customType, progressHandler: nil) { (fileMessage, error) in
    if error != nil {
        // Error!
        return
    }

    let first = fileMessage?.thumbnails?[0]
    let second = fileMessage?.thumbnails?[1]

    let maxSizeFirst = first?.maxSize
    let maxSizeSecond = second?.maxSize

    let urlFirst = first?.url
    let urlSecond = second?.url
}

maxWidth and maxHeight specify the maximum dimensions of the thumbnail. Your image will be scaled down evenly to fit within the bounds of (maxWidth, maxHeight). Note that if the original image is smaller than the specified dimensions, the thumbnail will not be scaled. url returns the location of the generated thumbnail file within the SendBird servers.

Channel Metadata

With MetaData and MetaCounter, you can store additional information within a channel.

MetaData allows you to store a NSDictionary of NSString key-value pairs in a channel instance.
If your aim is to store an integer with atomic increasing/decreasing operations, you should use a MetaCounter instead.

Use cases for MetaData/Counters could include tracking the number of likes, the background color, or a long description of the channel, which can each be fetched and rendered into the UI.

MetaData

MetaData is a NSDictionary that is stored within a channel. Its uses are very flexible, allowing you to customize a channel to fit you and your users' needs.

Create

Storing MetaData into a channel simply requires creation of a NSDictionary, then passing it as an argument when calling createMetaData:completionHandler:. You can store multiple key-value pairs in the dictionary.

Objective-C
[SBDOpenChannel getChannelWithUrl:channelUrl completionHandler:^(SBDOpenChannel * _Nonnull channel, SBDError * _Nullable error) {
    [channel createMetaData:newMetaData completionHandler:^(NSDictionary<NSString *,NSString *> * _Nullable metaData, SBDError * _Nullable error) {
        if (error != nil) {
            // Error case        
        }
        else {

        }
    }];
}];
Swift
SBDOpenChannel.getWithUrl(channelUrl) { (channel, error) in
    channel?.createMetaData(newMetaData, completionHandler: { (metaData, error) in
        if error != nil {
            // Error case
        }
        else {

        }
    })
}

Update

The process for updating MetaData is identical to creation. Values will be updated for existing keys, while new key-value pairs will be added.

Objective-C
[SBDOpenChannel getChannelWithUrl:channelUrl completionHandler:^(SBDOpenChannel * _Nonnull channel, SBDError * _Nullable error) {
    [channel updateMetaData:metaDataToUpdate completionHandler:^(NSDictionary<NSString *,NSObject *> * _Nullable metaData, SBDError * _Nullable error) {
        if (error != nil) {
            // Error case        
        }
        else {

        }            
    }];
}];
Swift
SBDOpenChannel.getWithUrl(channelUrl) { (channel, error) in
    channel?.updateMetaData(metaDataUpUpdate, completionHandler: { (metaData, error) in
        if error != nil {
            // Error case
        }
        else {

        }
    })
}

Get

Getting stored MetaData requires creating a NSArray of keys to pass as an argument to getMetaDataWithKeys:completionHandler:. The callback completionHandler returns a NSDictionary<NSString *, NSObject *> containing the corresponding key-value pairs.

Objective-C
[SBDOpenChannel getChannelWithUrl:channelUrl completionHandler:^(SBDOpenChannel * _Nonnull channel, SBDError * _Nullable error) {
    [channel getMetaDataWithKeys:@[@"channel_desc"] completionHandler:^(NSDictionary<NSString *,NSObject *> * _Nullable metaData, SBDError * _Nullable error) {
        if (error != nil) {
            // Error case        
        }
        else {

        }            
    }];
}];
Swift
SBDOpenChannel.getWithUrl(channelUrl) { (channel, error) in
    channel?.getMetaData(withKeys: ["channel_dest"], completionHandler: { (metaData, error) in
        if error != nil {
            // Error case
        }
        else {

        }
    })
}

MetaCounter

A MetaCounter is a NSDictionary that is stored within a channel instance. Its primary uses are to track and update discrete indicators within a channel.

Create

Storing a MetaCounter into a channel simply requires creation of a NSDictionary, then passing it as an argument when calling createMetaCounters:completionHandler:. You can store multiple key-value pairs in the dictionary.

Objective-C
[SBDOpenChannel getChannelWithUrl:channelUrl completionHandler:^(SBDOpenChannel * _Nonnull channel, SBDError * _Nullable error) {
    [channel createMetaCounters:newMetaCounters completionHandler:^(NSDictionary<NSString *,NSNumber *> * _Nullable metaCounters, SBDError * _Nullable error) {
        if (error != nil) {
            // Error case        
        } 
        else {

        }            
    }];
}];
Swift
SBDOpenChannel.getWithUrl(channelUrl) { (channel, error) in
    channel?.createMetaData(newMetaCounters, completionHandler: { (metaCounters, error) in
        if error != nil {
            // Error case
        }
        else {

        }
    })
}

Get

Retrieving stored MetaCounters requires creating a NSArray of keys to pass as an argument to getMetaCountersWithKeys:completionHandler:. The callback completionHandler returns a NSDictionary<NSString *,NSNumber *> containing the corresponding key-value pairs.

Objective-C
[SBDOpenChannel getChannelWithUrl:channelUrl completionHandler:^(SBDOpenChannel * _Nonnull channel, SBDError * _Nullable error) {
    [channel getMetaCountersWithKeys:@[@"like"] completionHandler:^(NSDictionary<NSString *,NSNumber *> * _Nullable metaCounters, SBDError * _Nullable error) {
        if (error != nil) {
            // Error case        
        } 
        else {

        }                             
    }];
}];
Swift
SBDOpenChannel.getWithUrl(channelUrl) { (channel, error) in
    channel?.getMetaCounters(withKeys: ["like"], completionHandler: { (metaCounters, error) in
        if error != nil {
            // Error case
        }
        else {

        }
    })
}

Increase

The increase and decrease operations work similarly to getting MetaCounters, as described above. Create a NSArray of keys to pass to increaseMetaCounters:completionHandler:, which increments the corresponding MetaCounters by 1.

Objective-C
[SBDOpenChannel getChannelWithUrl:channelUrl completionHandler:^(SBDOpenChannel * _Nonnull channel, SBDError * _Nullable error) {
    [channel increaseMetaCounters:deltaMetaCounters completionHandler:^(NSDictionary<NSString *,NSNumber *> * _Nullable metaCounters, SBDError * _Nullable error) {
        if (error != nil) {
            // Error case        
        } 
        else {

        }            
    }];
}];
Swift
SBDOpenChannel.getWithUrl(channelUrl) { (channel, error) in
    channel?.increaseMetaCounters(deltaMetaCounters, completionHandler: { (metaCounters, error) in
        if error != nil {
            // Error case
        }
        else {

        }
    })
}

Decrease

Likewise, pass a NSArray of keys to decreaseMetaCounters:completionHandler:, which decrements the MetaCounters by 1.

Objective-C
[SBDOpenChannel getChannelWithUrl:channelUrl completionHandler:^(SBDOpenChannel * _Nonnull channel, SBDError * _Nullable error) {
    [channel decreaseMetaCounters:deltaMetaCounters completionHandler:^(NSDictionary<NSString *,NSNumber *> * _Nullable metaCounters, SBDError * _Nullable error) {
        if (error != nil) {
            // Error case        
        } 
        else {

        }            
    }];
}];
Swift
SBDOpenChannel.getWithUrl(channelUrl) { (channel, error) in
    channel?.decreaseMetaCounters(deltaMetaCounters, completionHandler: { (metaCounters, error) in
        if error != nil {
            // Error case
        }
        else {

        }
    })
}

Event Handler

Event Handlers are crucial components of the SendBird SDK that allow a client to react to server-side events. These handlers contain callback methods that can be overridden to respond to specific chat-related events passed from the server. For example, channel:didReceiveMessage: of SBDChannelDelegate is triggered whenever a message is received. The specifics of each received message is contained within the SBDBaseChannel and SBDBaseMessage arguments passed back from the triggering callback.

By providing its own Event Handlers, the SendBird SDK allows a client to respond to asynchronous events without worrying about the plethora of issues surrounding client-server communication and multithreading. A chat application especially involves rapid exchanges of data that must take place in near real-time across potentially thousands of users. Therefore, the SDK optimizes communication and threading to ensure data integrity between users and servers. Add Event Handlers and implement the necessary callback methods to track events occurring within channels or a user's own device.

Channel Delegate

Register a SBDChannelDelegate to receive information whenever events occur within a channel.

You can register multiple Channel Delegates. UNIQUE_HANDLER_ID is a unique identifier that should be given to each delegate. Typically, Event Handlers would be registered in each view controller in order to stay up to date with changes in the channel, as well as notify the channel of the user's own view controller.

Objective-C
// ViewController.m
@interface ViewController : UIViewController<SBDConnectionDelegate, SBDChannelDelegate>

@end

@implementation ViewController

- (void)initViewController {
    // ...

    [SBDMain addChannelDelegate:self identifier:UNIQUE_HANDLER_ID];

    // ...
}

- (void)channel:(SBDBaseChannel * _Nonnull)sender didReceiveMessage:(SBDBaseMessage * _Nonnull)message {
    // Received a chat message
}

- (void)channelDidUpdateReadReceipt:(SBDGroupChannel * _Nonnull)sender {
    // When read receipt has been updated 
}

- (void)channelDidUpdateTypingStatus:(SBDGroupChannel * _Nonnull)sender {
    // When typing status has been updated
}

- (void)channel:(SBDGroupChannel * _Nonnull)sender userDidJoin:(SBDUser * _Nonnull)user {
    // When a new member joined the group channel
}

- (void)channel:(SBDGroupChannel * _Nonnull)sender userDidLeave:(SBDUser * _Nonnull)user {
    // When a member left the group channel
}

- (void)channel:(SBDOpenChannel * _Nonnull)sender userDidEnter:(SBDUser * _Nonnull)user {
    // When a new user entered the open channel
}

- (void)channel:(SBDOpenChannel * _Nonnull)sender userDidExit:(SBDUser * _Nonnull)user {
    // When a new user left the open channel
}

- (void)channel:(SBDOpenChannel * _Nonnull)sender userWasMuted:(SBDUser * _Nonnull)user {
    // When a user is muted on the open channel
}

- (void)channel:(SBDOpenChannel * _Nonnull)sender userWasUnmuted:(SBDUser * _Nonnull)user {
    // When a user is unmuted on the open channel
}

- (void)channel:(SBDOpenChannel * _Nonnull)sender userWasBanned:(SBDUser * _Nonnull)user {
    // When a user is banned on the open channel
}

- (void)channel:(SBDOpenChannel * _Nonnull)sender userWasUnbanned:(SBDUser * _Nonnull)user {
    // When a user is unbanned on the open channel
}

- (void)channelWasFrozen:(SBDOpenChannel * _Nonnull)sender {
    // When the open channel is frozen
}

- (void)channelWasUnfrozen:(SBDOpenChannel * _Nonnull)sender {
    // When the open channel is unfrozen
}

- (void)channelWasChanged:(SBDBaseChannel * _Nonnull)sender {
    // When a channel property has been changed
}

- (void)channelWasDeleted:(NSString * _Nonnull)channelUrl channelType:(SBDChannelType)channelType {
    // When a channel has been deleted
}

- (void)channel:(SBDBaseChannel * _Nonnull)sender messageWasDeleted:(long long)messageId {
    // When a message has been deleted
}

@end
Swift
// ViewController.swift
class ViewController: UIViewController, SBDConnectionDelegate, SBDChannelDelegate {
    func initViewController() {
        // ...
        SBDMain.add(self as SBDChannelDelegate, identifier: UNIQUE_HANDLER_ID)
        // ...
    }

    func channel(_ sender: SBDBaseChannel, didReceive message: SBDBaseMessage) {
        // Received a chat message
    }

    func channelDidUpdateReadReceipt(_ sender: SBDGroupChannel) {
        // When read receipt has been updated 
    }

    func channelDidUpdateTypingStatus(_ sender: SBDGroupChannel) {
        // When typing status has been updated
    }

    func channel(_ sender: SBDGroupChannel, userDidJoin user: SBDUser) {
        // When a new member joined the group channel
    }

    func channel(_ sender: SBDGroupChannel, userDidLeave user: SBDUser) {
        // When a member left the group channel
    }

    func channel(_ sender: SBDOpenChannel, userDidEnter user: SBDUser) {
        // When a new user entered the open channel
    }

    func channel(_ sender: SBDOpenChannel, userDidExit user: SBDUser) {
        // When a new user left the open channel
    }

    func channel(_ sender: SBDOpenChannel, userWasMuted user: SBDUser) {
        // When a user is muted on the open channel
    }

    func channel(_ sender: SBDOpenChannel, userWasUnmuted user: SBDUser) {
        // When a user is unmuted on the open channel
    }

    func channel(_ sender: SBDOpenChannel, userWasBanned user: SBDUser) {
        // When a user is banned on the open channel
    }

    func channel(_ sender: SBDOpenChannel, userWasUnbanned user: SBDUser) {
        // When a user is unbanned on the open channel
    }

    func channelWasFrozen(_ sender: SBDOpenChannel) {
        // When the open channel is frozen
    }

    func channelWasUnfrozen(_ sender: SBDOpenChannel) {
        // When the open channel is unfrozen
    }

    func channelWasChanged(_ sender: SBDBaseChannel) {
        // When a channel property has been changed
    }

    func channelWasDeleted(_ channelUrl: String, channelType: SBDChannelType) {
        // When a channel has been deleted
    }

    func channel(_ sender: SBDBaseChannel, messageWasDeleted messageId: Int64) {
        // When a message has been deleted
    }
}

channelWasChanged is called whenever a one of the following channel properties have been changed :

  • Push preference
  • Last message (except in cases where the message is a silent Admin message)
  • Unread message count
  • Name, cover image, data, Custom Type
  • Operators (only applicable to Open Channels)
  • Distinct property (only applicable to Group Channels)

You should remove the SBDChannelDelegate where the view controller is no longer valid.

Objective-C
[SBDMain removeChannelDelegateForIdentifier:UNIQUE_HANDLER_ID];
Swift
SBDMain.removeChannelDelegate(forIdentifier: UNIQUE_HANDLER_ID)

Connection Delegate

Register a SBDConnectionDelegate to detect changes in the user's own connection status.

You can register multiple Connection Delegates. UNIQUE_HANDLER_ID is a unique identifier that should be given to each delegate. Typically, Connection Delegates would be registered in each view controller in order to monitor the state of the user's connection with the SendBird servers.

Objective-C
// ViewController.m
@interface ViewController : UIViewController<SBDConnectionDelegate, SBDChannelDelegate>

@end

@implementation ViewController

- (void)initViewController {
    // ...

    [SBDMain addConnectionDelegate:self identifier:UNIQUE_HANDLER_ID];

    // ...
}

- (void)didStartReconnection {
    // Network has been disconnected. Auto reconnecting starts
}

- (void)didSucceedReconnection {
    // Auto reconnecting succeeded
}

- (void)didFailReconnection {
    // Auto reconnecting failed. You should call `connect` to reconnect to SendBird.
}
Swift
// ViewController.swift
class ViewController: UIViewController, SBDConnectionDelegate, SBDChannelDelegate {
    func initViewController() {
        // ...
        SBDMain.add(self as SBDConnectionDelegate, identifier: UNIQUE_HANDLER_ID)
        // ...
    }

    func didStartReconnection() {
        // Network has been disconnected. Auto reconnecting starts
    }

    func didSucceedReconnection() {
        // Auto reconnecting succeeded
    }

    func didFailReconnection() {
        // Auto reconnecting failed. You should call `connect` to reconnect to SendBird.
    }
}

You should remove the Connection Delegate where the view controller is no longer valid.

Objective-C
[SBDMain removeConnectionDelegateForIdentifier:UNIQUE_HANDLER_ID];
Swift
SBDMain.removeConnectionDelegate(forIdentifier: UNIQUE_HANDLER_ID)

Push Notifications for iOS

By setting up push notification service to an app, your app users can receive messages even when they are offline.

Typically, you might want users to receive push notifications after their app goes into the background. SendBird SDK automatically detects if your app enters the background and updates the user's connection status to Disconnected. Therefore, in normal cases, you do not have to call disconnect explicitly.

Push notifications are only supported in Group Channels. The SDK does not provide an option to receive push notifications from Open Channels.

Follow these 4 steps to enable push notifications for iOS.

  1. Create a Certificate Signing Request(CSR).
  2. Create a Push Notification SSL certificate in Apple Developer site.
  3. Export a p12 file and upload it to SendBird Dashboard.
  4. Register a device token in SendBird SDK and parse SendBird APNS messages.

Step 1: Create a Certificate Signing Request(CSR)

Open Keychain Access on your Mac (Applications -> Utilities -> Keychain Access). Select Request a Certificate From a Certificate Authority.

CSR1

In the Certificate Information window, do the following:

  • In the User Email Address field, enter your email address.
  • In the Common Name field, create a name for your private key (e.g., John Doe Dev Key).
  • The CA Email Address field should be left empty.
  • In the Request is group, select the Saved to disk option.

CSR2

Step 2: Create a Push Notification SSL certificate

Log in to the Apple Developer Member Center and find the Certificates, Identifiers & Profiles menu.
Select App IDs, find your target application, and click the Edit button.

App IDs

Enable Push Notifications and create a development or production certificate to fit your purpose.

Push Certificate

You should upload the CSR file that you created in section (1) in order to complete this process.
After doing so, you should be able to download a SSL certificate.

Double-click the file and register it to your login keychain.

Push Certificate2

Step 3: Export a p12 file and upload it to SendBird Dashboard.

Under Keychain Access, click the Certificates category from the left menu.
Find the Push SSL certificate you just registered and right-click it without expanding the certificate. Then select Export to save the file to your disk.

P12 export1

Keychain will ask you for a password, but just leave it empty.

It is very important that your p12 has no password.

P12 export2

Then, log in to the SendBird Dashboard and upload your p12 file to the Push Notification section, under Settings.

Set Push Information

Alternatively, you can register certificates via the Platform API.

Step 4: Register and unregister a device token in SendBird SDK

In your application's AppDelegate, store your device token as a variable.

Objective-C
// AppDelegate.m
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
    // ...

    // Save devToken to your own global variable.
    [SBDMain registerDevicePushToken:deviceToken unique:YES completionHandler:^(SBDPushTokenRegistrationStatus status, SBDError * _Nullable error) {
        if (error == nil) {
            if (status == SBDPushTokenRegistrationStatusPending) {
                // Registration is pending.
                // If you get this status, invoke `+ registerDevicePushToken:unique:completionHandler:` with `[SBDMain getPendingPushToken]` after connection.
            }
            else {
                // Registration succeeded.
            }
        }
        else {
            // Registration failed.
        }
    }];
Swift
// AppDelegate.swift
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    SBDMain.registerDevicePushToken(deviceToken, unique: true) { (status, error) in
        if error == nil {
            if status == SBDPushTokenRegistrationStatus.pending {
                // Registration is pending.
                // If you get this status, invoke `+ registerDevicePushToken:unique:completionHandler:` with `[SBDMain getPendingPushToken]` after connection.
            }
            else {
                // Registration succeeded.
            }
        }
        else {
            // Registration failed.
        }
    }
}

If your token registration status is pending, this means that your user has not yet connected when you had attempted to register the device token. In this case, register a completionHandler after your user has successfully connected in order to store the pending token.

To do this, invoke + registerDevicePushToken:unique:completionHandler: in a callback of + connectWithUserId:completionHandler: or + connectWithUserId:accessToken:completionHandler: of SBDMain.

Objective-C
[SBDMain connectWithUserId:USER_ID completionHandler:^(SBDUser * _Nullable user, SBDError * _Nullable error) {
    if (error == nil) {
        [SBDMain registerDevicePushToken:[SBDMain getPendingPushToken] unique:YES completionHandler:^(SBDPushTokenRegistrationStatus status, SBDError * _Nullable error) {

        }];
    }
}];
Swift
SBDMain.connect(withUserId: USER_ID, completionHandler: { (user, error) in 
    if error == nil {
        SBDMain.registerDevicePushToken(SBDMain.getPendingPushToken()!, unique: true, completionHandler: { (status, error) in

        })
    }
})

Don't forget to call the following code in the appropriate place to receive permissions from your users.

Objective-C
if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {
   UIUserNotificationSettings* notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
   [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
   [[UIApplication sharedApplication] registerForRemoteNotifications];
} else {
   [[UIApplication sharedApplication] registerForRemoteNotificationTypes: (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
}
Swift
let notificationSettings = UIUserNotificationSettings(types: [UIUserNotificationType.alert, UIUserNotificationType.badge, UIUserNotificationType.sound], categories: nil)
        UIApplication.shared.registerUserNotificationSettings(notificationSettings)
        UIApplication.shared.registerForRemoteNotifications()

SendBird APNS push notifications are sent with the following options.

  • alert : "{Sender Nickname}: {Text Message}"
  • sound : default
  • badge : total unread message count of each user

    You can disable the badge count from the SendBird Dashboard if you wish to.

SendBird also sends an additional payload with a sendbird key.
You can parse the payload within didReceiveRemoteNotification and use it to handle user reactions.

Objective-C
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler{

    NSString *alertMsg = [[userInfo objectForKey:@"aps"] objectForKey:@"alert"];
    NSDictionary *payload = [userInfo objectForKey:@"sendbird"];

      // Your custom way to parse data
    completionHandler(UIBackgroundFetchResultNewData);
}
Swift
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    let alertMsg = (userInfo["aps"] as! NSDictionary)["alert"] as! NSDictionary
    let payload = userInfo["sendbird"] as! NSDictionary

    // Your custom way to parse data
    completionHandler(UIBackgroundFetchResult.newData)
}

Here is the complete format of each payload.

{
  category: "messaging:offline_notification",
  type: string,              // Message Type, User or File or Admin
  message: string,           // User input message
  data: string,              // Custom data field
  app_id : string,           // application_id
  unread_message_count : int // Total unread count of the user
  channel: {
    channel_url: string,     // Group Channel URL
    name: string,            // Group Channel name
  },
  channel_type: string,      // messaging, group_messaging, chat
  sender: {
    id: string,              // Sender's unique ID
    name: string,            // Sender's nickname
    profile_url: string      // Sender's profile image url
  },
  recipient: {
    id: string,              // Recipient's unique ID
    name: string,            // Recipient's nickname
  },
  files: [],  // If a message is a file link, this array represents files
  translations: {} // If a message has translations, this dict has locale:translation.
}

Notification Preferences

Objective-C

Push notifications can be turned on or off. In order to turn notifications off for a specific user, call + registerDevicePushToken:unique:completionHandler:, + unregisterPushToken:completionHandler: and + unregisterAllPushTokenWithCompletionHandler:.

You should call the above methods after the user has established connection with SendBird through + connectionWithUserId:completionHandler: or + connectionWithUserId:accessToken:completionHandler:.

- (void)setPushNotification:(BOOL)enable {
    if (enable) {
        [SBDMain registerDevicePushToken:[SBDMain getPendingPushToken] unique:YES completionHandler:^(SBDPushTokenRegistrationStatus status, SBDError * _Nullable error) {
            if (error != nil) {
                NSLog(@"Error");
                return;
            }
        }];
    }
    else {
        // If you want to unregister the current device only, invoke this method.
        [SBDMain unregisterPushToken:[SBDMain getPendingPushToken] completionHandler:^(NSDictionary * _Nullable response, SBDError * _Nullable error) {
            if (error != nil) {
                NSLog(@"Error");
                return;
            }
        }];

        // If you want to unregister the all devices of the user, invoke this method.
        [SBDMain unregisterAllPushTokenWithCompletionHandler:^(NSDictionary * _Nullable response, SBDError * _Nullable error) {
            if (error != nil) {
                NSLog(@"Error");
                return;
            }
        }];
    }
}
Swift

Push notifications can be turned on or off. In order to turn notifications off for a specific user, call registerDevicePushToken(_ devToken: Data, unique: Bool, completionHandler: ((SBDPushTokenRegistrationStatus, SBDError?) -> Swift.Void)?, unregisterPushToken(devToken: Data, completionHandler: (([AnyHashable : Any]?, SBDError?) -> Void)?) and + unregisterAllPushToken(completionHandler: (([AnyHashable : Any]?, SBDError?) -> Void)?).

You should call the above methods after the user has established connection with SendBird through connect(withUserId: String, completionHandler: ((SBDUser?, SBDError?) -> Void)?) or connect(withUserId: String, accessToken: String?, completionHandler: ((SBDUser?, SBDError?) -> Void)?).

func setPushNotification(enable: Bool) {
    if enable {
        SBDMain.registerDevicePushToken(SBDMain.getPendingPushToken()!, unique: true, completionHandler: { (status, error) in
            if error != nil {
                NSLog("Error")
                return
            }
        })
    }
    else {
        // If you want to unregister the current device only, invoke this method.
        SBDMain.unregisterPushToken(SBDMain.getPendingPushToken()!, completionHandler: { (response, error) in
            if error != nil {
                NSLog("Error")
                return
            }
        })

        // If you want to unregister the all devices of the user, invoke this method.
        SBDMain.unregisterAllPushToken(completionHandler: { (response, error) in
            if error != nil {
                NSLog("Error")
                return
            }
        })
    }
}

You can also change push notification settings for a specific Group Channel.

Objective-C
// If you want to turn push notification for this channel on, set this `YES`.
[channel setPushPreferenceWithPushOn:YES_OR_NO completionHandler:^(SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error");
        return;
    }
}];
Swift
// If you want to turn push notification for this channel on, set this `true`.
channel.setPushPreferenceWithPushOn(true_or_false) { (error) in
    if error != nil {
        NSLog("Error")
        return
    }
}

If you want to snooze alerts (notifications) for some periods, use setDoNotDisturb.

Objective-C
// The current logged-in user doesn't receive push notifications during the specified time.
[SBDMain setDoNotDisturbWithEnable:YES_OR_NO startHour:START_HOUR startMin:START_MIN endHour:END_HOUR endMin:END_MIN timezone:TIMEZONE completionHandler:^(SBDError * _Nullable error) {
    if (error != nil) {
        NSLog(@"Error");
        return;
    }
}];
Swift
// The current logged-in user doesn't receive push notifications during the specified time.
SBDMain.setDoNotDisturbWithEnable(true_or_false, startHour: START_HOUR, startMin: START_MIN, endHour: END_HOUR, endMin: END_MIN, timezone: TIMEZONE) { (error) in
    if error != nil {
        NSLog("Error")
        return
    }    
}

Push notification message templates

Message templates define how a message is displayed when a push notification arrives to a user's device. You can choose between the default template and the alternative template, both of which are customizable.

Message templates
Text Message File Message
Default template {sender_name}: {message} (e.g., John: Hello!) {filename} (e.g., squirrel.jpg)
Alternative template New message arrived New file arrived

{sender_name}, {message}, and {filename} are variables that represent the corresponding string values. Use these fields to customize message templates from your Dashboard Settings. The option is under Notifications - Push Notification Message Templates.

To choose whether a user receives messages in the form of the default template or the alternative template, call [SBDMain setPushTemplateWithName:completionHandler:]. setPushTemplateWithName can be one of two values: SBD_PUSH_TEMPLATE_DEFAULT, or SBD_PUSH_TEMPLATE_ALTERNATIVE.

Objective-C
[SBDMain setPushTemplateWithName:SBD_PUSH_TEMPLATE_ALTERNATIVE completionHandler:^(SBDError * _Nullable error) {
    if (error != nil) {
        // Error!
        return;
    }

    // Push template successfully set to SBD_PUSH_TEMPLATE_ALTERNATIVE.
}];
Swift
SBDMain.setPushTemplateWithName(SBD_PUSH_TEMPLATE_ALTERNATIVE) { (error) in
    if error != nil {
        // Error!
        return
    }

    // Push template successfully set to SBD_PUSH_TEMPLATE_ALTERNATIVE.
}

Note that the default configuration is SBD_PUSH_TEMPLATE_DEFAULT.

You can check your current setting with [SBDMain getPushTemplateWithCompletionHandler:].

Objective-C
[SBDMain getPushTemplateWithCompletionHandler:^(NSString * _Nullable name, SBDError * _Nullable error) {
    if (error != nil) {
        // Error!
        return;
    }

    if ([name isEqualToString:SBD_PUSH_TEMPLATE_DEFAULT]) {
        // Currently configured to use the default template.
    }
    else if ([name isEqualToString:SBD_PUSH_TEMPLATE_ALTERNATIVE]) {
        // Currently configured to use the alternative template.
    }
}];
Swift
SBDMain.getPushTemplate { (name, error) in
    if error != nil {
        // Error!
        return
    }

    if name == SBD_PUSH_TEMPLATE_DEFAULT {
        // Currently configured to use the default template.
    }
    else if name == SBD_PUSH_TEMPLATE_ALTERNATIVE {
        // Currently configured to use the alternative template.
    }
}

Caching Data

Storing a local copy of SendBird data in a device enables users to look through their messages and channels even while offline. It can also prevent the inefficiency of repeating queries upon each connection or device state change, as well as provide a smoother user experience by reducing data loading delays.

In this document, we will guide you on building a local cache using object serialization/deserialization, which is provided through the SDK. In the Basic caching using a file section, we provide instructions on building a simple cache that stores the most recent messages and channels in a file. In the Advanced caching using a database section, you can find instructions on caching data in an internal database, which enables you to store structured and queriable data.

Basic caching using a file

In this section, we will guide you on building a simple cache that stores a user's most recent messages and channels. This cache can be used to load data when a user views their channel list, or enters a channel to view their message history. Implementing even a basic cache such as this can greatly improve user experience, as users will no longer encounter empty lists of channels or messages when their connectivity is unstable.

In the steps described below, we will create a file per channel in the application's cache directory. Next, we will write serialized data into the file to store a set amount of recent messages. Finally, we will configure the app to first load messages from the cache, then replace them when the newest results are successfully fetched from the servers.

Step 1: Object serialization / deserialization

In order to store SendBird objects such as messages, channels, and users in local storage, we provide serialization and deserialization methods through our SDK. Use serialize to convert a SendBird object to binary data, which can then be natively stored in a file.

// In SBDBaseMessage.h
- (nullable NSData *)serialize;
+ (nullable instancetype)buildFromSerializedData:(NSData * _Nonnull)data;

// In SBDBaseChannel.h
- (nullable NSData *)serialize;
+ (nullable instancetype)buildFromSerializedData:(NSData * _Nonnull)data;

Step 2: Saving messages

With serialization, you can store a channel and its most recent messages in a file. In this case, we are encoding the binary serialized data into a Base64 string. then storing each item in a new line. Normally, you would save data when onStop() is called in your user's chat screen.

+ (void)saveMessages:(NSArray<SBDBaseMessage *> * _Nonnull)messages channelUrl:(NSString * _Nonnull)channelUrl{
    // Serialize messages
    NSUInteger startIndex = 0;

    if (messages.count == 0) {
        return;
    }

    if (messages.count > 100) {
        startIndex = messages.count - 100;
    }

    NSMutableArray<NSString *> *serializedMessages = [[NSMutableArray alloc] init];
    for (; startIndex < messages.count; startIndex++) {
        NSString *requestId = nil;
        if ([messages[startIndex] isKindOfClass:[SBDUserMessage class]]) {
            requestId = ((SBDUserMessage *)messages[startIndex]).requestId;
        }
        else if ([messages[startIndex] isKindOfClass:[SBDFileMessage class]]) {
            requestId = ((SBDFileMessage *)messages[startIndex]).requestId;
        }

        NSData *messageData = [messages[startIndex] serialize];
        NSString *messageString = [messageData base64EncodedStringWithOptions:0];
        [serializedMessages addObject:messageString];
    }

    NSString *dumpedMessages = [serializedMessages componentsJoinedByString:@"\n"];
    NSString *dumpedMessagesHash = [[self class] sha256:dumpedMessages];

    // Save messages to temp file.
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *appIdDirectory = [documentsDirectory stringByAppendingPathComponent:[SBDMain getApplicationId]];

    NSString *uniqueTempFileNamePrefix = [[NSUUID UUID] UUIDString];
    NSString *tempMessageDumpFileName = [NSString stringWithFormat:@"%@.data", uniqueTempFileNamePrefix];
    NSString *tempMessageHashFileName = [NSString stringWithFormat:@"%@.hash", uniqueTempFileNamePrefix];

    NSString *tempMessageDumpFilePath = [appIdDirectory stringByAppendingPathComponent:tempMessageDumpFileName];
    NSString *tempMessageHashFilePath = [appIdDirectory stringByAppendingPathComponent:tempMessageHashFileName];

    NSError *errorCreateDirectory = nil;
    if ([[NSFileManager defaultManager] fileExistsAtPath:appIdDirectory] == NO) {
        [[NSFileManager defaultManager] createDirectoryAtPath:appIdDirectory withIntermediateDirectories:NO attributes:nil error:&errorCreateDirectory];
    }

    if (errorCreateDirectory != nil) {
        return;
    }

    NSString *messageFileNamePrefix = [[self class] sha256:[NSString stringWithFormat:@"%@_%@", [SBDMain getCurrentUser].userId, channelUrl]];
    NSString *messageDumpFileName = [NSString stringWithFormat:@"%@.data", messageFileNamePrefix];
    NSString *messageHashFileName = [NSString stringWithFormat:@"%@.hash", messageFileNamePrefix];

    NSString *messageDumpFilePath = [appIdDirectory stringByAppendingPathComponent:messageDumpFileName];
    NSString *messageHashFilePath = [appIdDirectory stringByAppendingPathComponent:messageHashFileName];

    // Check hash.
    NSString *previousHash;
    if (![[NSFileManager defaultManager] fileExistsAtPath:messageDumpFilePath]) {
        [[NSFileManager defaultManager] createFileAtPath:messageDumpFilePath contents:nil attributes:nil];
    }

    if (![[NSFileManager defaultManager] fileExistsAtPath:messageHashFilePath]) {
        [[NSFileManager defaultManager] createFileAtPath:messageHashFilePath contents:nil attributes:nil];
    }
    else {
        previousHash = [NSString stringWithContentsOfFile:messageHashFilePath encoding:NSUTF8StringEncoding error:nil];
    }

    if (previousHash != nil && [previousHash isEqualToString:dumpedMessagesHash]) {
        return;
    }

    // Write temp file.
    NSError *errorDump = nil;
    NSError *errorHash = nil;
    [dumpedMessages writeToFile:tempMessageDumpFilePath atomically:NO encoding:NSUTF8StringEncoding error:&errorDump];
    [dumpedMessagesHash writeToFile:tempMessageHashFilePath atomically:NO encoding:NSUTF8StringEncoding error:&errorHash];

    // Move temp to real file.
    if (errorDump == nil && errorHash == nil) {
        NSError *errorMoveDumpFile;
        NSError *errorMoveHashFile;

        [[NSFileManager defaultManager] removeItemAtPath:messageDumpFilePath error:nil];
        [[NSFileManager defaultManager] moveItemAtPath:tempMessageDumpFilePath toPath:messageDumpFilePath error:&errorMoveDumpFile];

        [[NSFileManager defaultManager] removeItemAtPath:messageHashFilePath error:nil];
        [[NSFileManager defaultManager] moveItemAtPath:tempMessageHashFilePath toPath:messageHashFilePath error:&errorMoveHashFile];

        if (errorMoveDumpFile != nil || errorMoveHashFile != nil) {
            [[NSFileManager defaultManager] removeItemAtPath:tempMessageDumpFilePath error:nil];
            [[NSFileManager defaultManager] removeItemAtPath:tempMessageHashFilePath error:nil];
            [[NSFileManager defaultManager] removeItemAtPath:messageDumpFilePath error:nil];
            [[NSFileManager defaultManager] removeItemAtPath:messageHashFilePath error:nil];
        }
    }
}

In this case, SHA256 hashing is used to generate a hash file for each stored data file. Using this hash file, you can check if the newly generated data differs from the one already stored in the cache, preventing unnecessary overwriting.

Step 3: Loading messages

When your user enters a chat to view their message history, load saved messages from the cache.

+ (nullable NSArray<SBDBaseMessage *> *)loadMessagesInChannel:(NSString * _Nonnull)channelUrl {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *appIdDirectory = [documentsDirectory stringByAppendingPathComponent:[SBDMain getApplicationId]];
    NSString *messageFileNamePrefix = [[self class] sha256:[NSString stringWithFormat:@"%@_%@", [SBDMain getCurrentUser].userId, channelUrl]];
    NSString *dumpFileName = [NSString stringWithFormat:@"%@.data", messageFileNamePrefix];
    NSString *dumpFilePath = [appIdDirectory stringByAppendingPathComponent:dumpFileName];

    if (![[NSFileManager defaultManager] fileExistsAtPath:dumpFilePath]) {
        return nil;
    }

    NSError *errorReadDump;
    NSString *messageDump = [NSString stringWithContentsOfFile:dumpFilePath encoding:NSUTF8StringEncoding error:&errorReadDump];

    if (messageDump.length > 0) {
        NSArray *loadMessages = [messageDump componentsSeparatedByString:@"\n"];

        if (loadMessages.count > 0) {
            NSMutableArray<SBDBaseMessage *> *messages = [[NSMutableArray alloc] init];
            for (NSString *msgString in loadMessages) {
                NSData *msgData = [[NSData alloc] initWithBase64EncodedString:msgString options:0];


                SBDBaseMessage *message = [SBDBaseMessage buildFromSerializedData:msgData];
                [messages addObject:message];
            }

            return messages;
        }
    }

    return nil;
}

After receiving an updated message list from the SendBird servers, clear the current message list and replace it with the updated list. In effect, messages from the cache would be overwritten almost instantly if the user's connection is normal.

Step 4: Saving and loading channels

The process of caching channels is identical to caching messages. For the sake of brevity, an implementation will be provided without additional explanations.

+ (void)saveChannels:(NSArray<SBDBaseChannel *> * _Nonnull)channels {
    // Serialize channels
    NSUInteger startIndex = 0;

    if (channels.count == 0) {
        return;
    }

    if (channels.count > 100) {
        startIndex = channels.count - 100;
    }

    NSMutableArray<NSString *> *serializedChannels = [[NSMutableArray alloc] init];
    for (; startIndex < channels.count; startIndex++) {
        NSData *channelData = [channels[startIndex] serialize];
        NSString *channelString = [channelData base64EncodedStringWithOptions:0];
        [serializedChannels addObject:channelString];
    }

    NSString *dumpedChannels = [serializedChannels componentsJoinedByString:@"\n"];
    NSString *dumpedChannelsHash = [[self class] sha256:dumpedChannels];

    // Save messages to temp file.
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *appIdDirectory = [documentsDirectory stringByAppendingPathComponent:[SBDMain getApplicationId]];

    NSString *uniqueTempFileNamePrefix = [[NSUUID UUID] UUIDString];
    NSString *tempChannelDumpFileName = [NSString stringWithFormat:@"%@_channellist.data", uniqueTempFileNamePrefix];
    NSString *tempChannelHashFileName = [NSString stringWithFormat:@"%@_channellist.hash", uniqueTempFileNamePrefix];

    NSString *tempChannelDumpFilePath = [appIdDirectory stringByAppendingPathComponent:tempChannelDumpFileName];
    NSString *tempChannelHashFilePath = [appIdDirectory stringByAppendingPathComponent:tempChannelHashFileName];

    NSError *errorCreateDirectory = nil;
    if ([[NSFileManager defaultManager] fileExistsAtPath:appIdDirectory] == NO) {
        [[NSFileManager defaultManager] createDirectoryAtPath:appIdDirectory withIntermediateDirectories:NO attributes:nil error:&errorCreateDirectory];
    }

    if (errorCreateDirectory != nil) {
        return;
    }

    NSString *channelFileNamePrefix = [NSString stringWithFormat:@"%@_channellist", [[self class] sha256:[SBDMain getCurrentUser].userId]];
    NSString *channelDumpFileName = [NSString stringWithFormat:@"%@.data", channelFileNamePrefix];
    NSString *channelHashFileName = [NSString stringWithFormat:@"%@.hash", channelFileNamePrefix];

    NSString *channelDumpFilePath = [appIdDirectory stringByAppendingPathComponent:channelDumpFileName];
    NSString *channelHashFilePath = [appIdDirectory stringByAppendingPathComponent:channelHashFileName];

    // Check hash.
    NSString *previousHash;
    if (![[NSFileManager defaultManager] fileExistsAtPath:channelDumpFilePath]) {
        [[NSFileManager defaultManager] createFileAtPath:channelDumpFilePath contents:nil attributes:nil];
    }

    if (![[NSFileManager defaultManager] fileExistsAtPath:channelHashFilePath]) {
        [[NSFileManager defaultManager] createFileAtPath:channelHashFilePath contents:nil attributes:nil];
    }
    else {
        previousHash = [NSString stringWithContentsOfFile:channelHashFilePath encoding:NSUTF8StringEncoding error:nil];
    }

    if (previousHash != nil && [previousHash isEqualToString:dumpedChannelsHash]) {
        return;
    }

    // Write temp file.
    NSError *errorDump = nil;
    NSError *errorHash = nil;
    [dumpedChannels writeToFile:tempChannelDumpFilePath atomically:NO encoding:NSUTF8StringEncoding error:&errorDump];
    [dumpedChannelsHash writeToFile:tempChannelHashFilePath atomically:NO encoding:NSUTF8StringEncoding error:&errorHash];

    // Move temp to real file.
    if (errorDump == nil && errorHash == nil) {
        NSError *errorMoveDumpFile;
        NSError *errorMoveHashFile;

        [[NSFileManager defaultManager] removeItemAtPath:channelDumpFilePath error:nil];
        [[NSFileManager defaultManager] moveItemAtPath:tempChannelDumpFilePath toPath:channelDumpFilePath error:&errorMoveDumpFile];

        [[NSFileManager defaultManager] removeItemAtPath:channelHashFilePath error:nil];
        [[NSFileManager defaultManager] moveItemAtPath:tempChannelHashFilePath toPath:channelHashFilePath error:&errorMoveHashFile];

        if (errorMoveDumpFile != nil || errorMoveHashFile != nil) {
            [[NSFileManager defaultManager] removeItemAtPath:tempChannelDumpFilePath error:nil];
            [[NSFileManager defaultManager] removeItemAtPath:tempChannelHashFilePath error:nil];
            [[NSFileManager defaultManager] removeItemAtPath:channelDumpFilePath error:nil];
            [[NSFileManager defaultManager] removeItemAtPath:channelHashFilePath error:nil];
        }
    }
}
+ (nullable NSArray<SBDGroupChannel *> *)loadGroupChannels {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *channelFileNamePrefix = [NSString stringWithFormat:@"%@_channellist", [[self class] sha256:[SBDMain getCurrentUser].userId]];
    NSString *dumpFileName = [NSString stringWithFormat:@"%@.data", channelFileNamePrefix];
    NSString *appIdDirectory = [documentsDirectory stringByAppendingPathComponent:[SBDMain getApplicationId]];
    NSString *dumpFilePath = [appIdDirectory stringByAppendingPathComponent:dumpFileName];

    if (![[NSFileManager defaultManager] fileExistsAtPath:dumpFilePath]) {
        return nil;
    }

    NSError *errorReadDump;
    NSString *channelDump = [NSString stringWithContentsOfFile:dumpFilePath encoding:NSUTF8StringEncoding error:&errorReadDump];

    if (channelDump.length > 0) {
        NSArray *loadChannels = [channelDump componentsSeparatedByString:@"\n"];

        if (loadChannels.count > 0) {
            NSMutableArray<SBDGroupChannel *> *channels = [[NSMutableArray alloc] init];
            for (NSString *channelString in loadChannels) {
                NSData *channelData = [[NSData alloc] initWithBase64EncodedString:channelString options:0];

                SBDGroupChannel *channel = [SBDGroupChannel buildFromSerializedData:channelData];
                [channels addObject:channel];
            }

            return channels;
        }
    }

    return nil;
}

Advanced caching using a database

In this section, we will guide you on building your own local cache using a database. This has several advantages to storing raw data in a file, notably enabling queries on stored channels and messages. While here we base our examples on SQLite, it shouldn't be too difficult to follow these steps with any database of your choice, such as Realm.

Step 1: Object serialization / deserialization

In order to store SendBird objects such as messages, channels, and users in local storage, we provide serialization and deserialization methods through our SDK. Use serialize to convert a SendBird object to binary data, which can then be natively stored in your persistent database.

// In SBDBaseMessage.h
- (nullable NSData *)serialize;
+ (nullable instancetype)buildFromSerializedData:(NSData * _Nonnull)data;

// In SBDBaseChannel.h
- (nullable NSData *)serialize;
+ (nullable instancetype)buildFromSerializedData:(NSData * _Nonnull)data;

Step 2: Caching messages

Table structure

A basic table to store messages would contain the following columns:

message_id channel_url message_ts payload
123123 sendbird_channel_234802384 1432039402493 Serialized data
234234 sendbird_channel_234802384 1432039403243 Serialized data

Caching procedure

  1. After fetching new messages using a – getNextMessagesByTimestamp:limit:reverse:completionHandler:, – getPreviousMessagesByTimestamp:limit:reverse:completionHandler:, and – getPreviousAndNextMessagesByTimestamp:prevLimit:nextLimit:reverse:completionHandler:, serialize and insert each message into your database. However, we do recommend storing the message ID, timestamp, and channel URL in separate columns (i.e. using message.messageId, message.createdAt, and message.channelUrl. This will allow you to query the dataset later on.

  2. Before loading messages within a channel, order rows chronologically by message_ts. Then, deserialize each message and display them in your UI.

  3. When loading previous messages that are not currently stored in the local database, obtain the timestamp of the earliest stored message. Then, query for messages created before that value.

  4. Likewise, when loading new messages, query for messages with a later timestamp than the most recent message.

Example 1 - When entering a channel
// Get messages from local database
sqlite3 *contactDB;
char *dbpath = "<DATABASE_PATH>";
char *query;
sqlite3_stmt *stmt;

if (sqlite3_open(dbpath, &contactDB) == SQLITE_OK) {
    if (order) {
        query = "SELECT * FROM (SELECT * FROM MESSAGE WHERE COLUMN_NAME_CHANNEL_URL = ? AND COLUMN_NAME_TIMESTAMP < ? ORDER BY COLUMN_NAME_TIMESTAMP DESC LIMIT ?) ORDER BY COLUMN_NAME_TIMESTAMP ASC";
    }
    else {
        query = "SELECT * FROM (SELECT * FROM MESSAGE WHERE COLUMN_NAME_CHANNEL_URL = ? AND COLUMN_NAME_TIMESTAMP < ? ORDER BY COLUMN_NAME_TIMESTAMP DESC LIMIT ?) ORDER BY COLUMN_NAME_TIMESTAMP DESC";
    }

    if (sqlite3_prepare_v2(contactDB, query, -1, &stmt, nil) == SQLITE_OK) {
        sqlite3_bind_text(stmt, 1, [@"<CHANNEL_URL>" UTF8String], -1, SQLITE_TRANSIENT); // COLUMN_NAME_CHANNEL_URL
        sqlite3_bind_int64(stmt, 2, timestamp); // COLUMN_NAME_TIMESTAMP
        sqlite3_bind_int64(stmt, 3, limit); // COLUMN_NAME_TIMESTAMP

        // Create a List of SBDBaseMessage by deserializing each item.
        NSMutableArray<SBDBaseMessage *> *prevMessageList = [[NSMutableArray alloc] init];

        while (sqlite3_step(stmt) == SQLITE_ROW) {
            const char *payload = sqlite3_column_blob(stmt, 4);

            int size = sqlite3_column_bytes(stmt, 4);
            data = [[NSData alloc] initWithBytes:payload length:size];

            SBDBaseMessage *message = [SBDBaseMessage buildFromSerializedData:data];
            [prevMessageList addObject:message];
        }
        sqlite3_finalize(stmt);

        // Pass messages to data source in order to display them in a UITableView, UICollectionView, etc.
        [self.messageList addObjects:prevMessageList];

        // Get new messages from the SendBird servers
        long latestStoredTs = prevMessageList[0].createdAt; // Get the timestamp of the last stored message.

        [self.channel getNextMessagesByTimestamp:latestStoredTs limit:30 reverse:NO completionHandler:^(NSArray<SBDBaseMessage *> * _Nullable messages, SBDError * _Nullable error) {
            if (error != nil) {
                // Error!
                return;
            }

            // New messages successfully fetched.
            [self.messageList addObjects:messages];

            // Insert each new message in your local database
            const char *query = "INSERT INTO MESSAGE (message_id, channel_url, message_ts, payload) VALUES (?, ?, ?, ?)";

            for (SBDBaseMessage *message in messages) {
                // Store each new message into the local database
                if (sqlite3_prepare_v2(contactDB, query, -1, &stmt, nil) == SQLITE_OK) {
                    sqlite3_bind_int64(stmt, 1, message.messageId); // message_id
                    sqlite3_bind_text(stmt, 2, [message.channelUrl UTF8String], -1, SQLITE_TRANSIENT); // channel_url
                    sqlite3_bind_int64(stmt, 3, message.createdAt); // message_ts

                    NSData *blob = [message serialize];
                    sqlite3_bind_blob(stmt, 4, [blob bytes], [blob length], SQLITE_TRANSIENT);
                }    

                if (sqlite3_step(stmt) != SQLITE_DONE) {
                    // Error!
                }

                sqlite3_finalize(stmt);
            }
        }];
    }

    sqlite3_finalize(stmt);
}

sqlite3_close(contactDB);
Example 2 - When receiving new messages
- (void)channel:(SBDBaseChannel * _Nonnull)sender didReceiveMessage:(SBDBaseMessage * _Nonnull)message {
    if (sender == self.channel) {
        // Pass the message to your data source for UITableView or UICollectionView.
        [self.messageList addObject:message];

        // Store the message in your local database.
        // Having a helper class or method for database transactions would probably be a good idea.
        sqlite3 *contactDB;
        char *dbpath = "<DATABASE_PATH>";
        char *query;
        sqlite3_stmt *stmt;

        if (sqlite3_open(dbpath, &contactDB) == SQLITE_OK) {
            // Insert each new message in your local database
            const char *query = "INSERT INTO MESSAGE (message_id, channel_url, message_ts, payload) VALUES (?, ?, ?, ?)";

            // Store each new message into the local database
            if (sqlite3_prepare_v2(contactDB, query, -1, &stmt, nil) == SQLITE_OK) {
                sqlite3_bind_int64(stmt, 1, message.messageId); // message_id
                sqlite3_bind_text(stmt, 2, [message.channelUrl UTF8String], -1, SQLITE_TRANSIENT); // channel_url
                sqlite3_bind_int64(stmt, 3, message.createdAt); // message_ts

                NSData *blob = [message serialize];
                sqlite3_bind_blob(stmt, 4, [blob bytes], [blob length], SQLITE_TRANSIENT);
            }    

            if (sqlite3_step(stmt) != SQLITE_DONE) {
                // Error!
            }

            sqlite3_finalize(stmt);
        }

        sqlite3_close(contactDB);
    }
}
Example 3 - When sending a message
[self.channel sendUserMessage:messageBody completionHandler:^(SBDUserMessage * _Nullable userMessage, SBDError * _Nullable error) {
    if (error != nil) {
        // Error!
        return;
    }

    // Pass the message to your data source for UITableView or UICollectionView.
    [self.messageList addObject:message];

    // Store the message in your local database.
    // Having a helper class or method for database transactions would probably be a good idea.
    sqlite3 *contactDB;
    char *dbpath = "<DATABASE_PATH>";
    char *query;
    sqlite3_stmt *stmt;

    if (sqlite3_open(dbpath, &contactDB) == SQLITE_OK) {
        // Insert each new message in your local database
        const char *query = "INSERT INTO MESSAGE (message_id, channel_url, message_ts, payload) VALUES (?, ?, ?, ?)";

        // Store each new message into the local database
        if (sqlite3_prepare_v2(contactDB, query, -1, &stmt, nil) == SQLITE_OK) {
            sqlite3_bind_int64(stmt, 1, message.messageId); // message_id
            sqlite3_bind_text(stmt, 2, [message.channelUrl UTF8String], -1, SQLITE_TRANSIENT); // channel_url
            sqlite3_bind_int64(stmt, 3, message.createdAt); // message_ts

            NSData *blob = [message serialize];
            sqlite3_bind_blob(stmt, 4, [blob bytes], [blob length], SQLITE_TRANSIENT);
        }    

        if (sqlite3_step(stmt) != SQLITE_DONE) {
            // Error!
        }

        sqlite3_finalize(stmt);
    }

    sqlite3_close(contactDB);
}];

Caveats

Currently, it is difficult to sync deleted or edited messages. We are working to provide this feature in both our SDKs and APIs, and hope to release it soon.

Step 3: Caching channels

Note: the examples in this section are based on Group Channels. To cache Open Channels, you would have to slightly improvise from the directions below (such as changing last_message_ts to channel_created_at).

Table structure

A basic table to store channels would contain the following columns:

channel_url last_message_ts payload
sendbird_channel_234802384 1432039402729 Serialized data
sendbird_channel_234802384 1432039403448 Serialized data

Caching procedure

  1. After fetching new channels using a SBDOpenChannelListQuery or SBDGroupChannelListQuery, serialize and insert each channel into your database. As with messages, we recommend storing the channel URL and timestamp of the last message in separate columns (i.e. using channel.channelUrl and channel.lastMessage.createdAt. This will allow you to query the dataset later on.

  2. Before loading a list of channels, order rows chronologically by last_message_ts. Then, deserialize each channel and display them in your UI.

  3. Unlike messages, channels are relatively few in number and go through frequent property changes, such as cover URL changes, name changes, or deletions. Therefore, we recommend updating your cache by completely replacing the dataset when possible.

  4. When real-time changes are made to a channel list, update your cache.

Example 1 - When entering the channel list screen
// Load channels from local database
sqlite3 *contactDB;
char *dbpath = "<DATABASE_PATH>";
char *query;
sqlite3_stmt *stmt;

if (sqlite3_open(dbpath, &contactDB) == SQLITE_OK) {
    if (order) {
        query = "SELECT * FROM CHANNEL ORDER BY COLUMN_NAME_LAST_MESSAGE_TIMESTAMP ASC";
    }
    else {
        query = "SELECT * FROM CHANNEL ORDER BY COLUMN_NAME_LAST_MESSAGE_TIMESTAMP DESC";
    }

    if (sqlite3_prepare_v2(contactDB, query, -1, &stmt, nil) == SQLITE_OK) {
        // Create a List of `SBDBaseChannel`s by deserializing each item.
        NSMutableArray<SBDBaseChannel *> *prevChannelList = [[NSMutableArray alloc] init];

        while (sqlite3_step(stmt) == SQLITE_ROW) {
            const char *payload = sqlite3_column_blob(stmt, 3);

            int size = sqlite3_column_bytes(stmt, 3);
            data = [[NSData alloc] initWithBytes:payload length:size];

            SBDBaseChannel *channel = [SBDBaseChannel buildFromSerializedData:data];
            [prevChannelList addObject:channel];
        }
        sqlite3_finalize(stmt);

        sqlite3_close(contactDB);

        // Pass messages to data source in order to display them in a UITableView, UICollectionView, etc.
        [self.channelList addObjects:prevChannelList];

        // Get new channels from the SendBird servers
        SBDGroupChannelListQuery *query = [SBDGroupChannel createMyGroupChannelListQuery];
        [query loadNextPageWithCompletionHandler:^(NSArray<SBDGroupChannel *> * _Nullable channels, SBDError * _Nullable error) {
            if (error != nil) {
                // Error!
                return;
            }

            // Replace the current (cached) dataset
            [self.channelList removeAllObjects];
            [self.channels addObjects:channels];

            sqlite3 *contactDB;
            char *dbpath = "<DATABASE_PATH>";
            char *query;
            sqlite3_stmt *stmt;

            // Clear the current cache


            if (sqlite3_open(dbpath, &contactDB) == SQLITE_OK) {
                // Insert each new channel in your local database
                const char *query = "INSERT INTO CHANNEL (channel_url, last_message_ts, payload) VALUES (?, ?, ?)";
                for (SBDGroupChannel *channel in channels) {
                    // Store each new channel into the local database
                    if (sqlite3_prepare_v2(contactDB, query, -1, &stmt, nil) == SQLITE_OK) {
                        sqlite3_bind_text(stmt, 1, [channel.channelUrl UTF8String], -1, SQLITE_TRANSIENT); // channel_url
                        sqlite3_bind_int64(stmt, 2, channel.lastMessage.createdAt); // last_message_ts

                        NSData *blob = [channel serialize];
                        sqlite3_bind_blob(stmt, 3, [blob bytes], [blob length], SQLITE_TRANSIENT);
                    }    

                    if (sqlite3_step(stmt) != SQLITE_DONE) {
                        // Error!
                    }

                    sqlite3_finalize(stmt);
                }
            }

            sqlite3_close(contactDB);
        }];
    }
}
Example 2 - On real-time events such as additions or updates
- (void)channelWasChanged:(SBDBaseChannel * _Nonnull)sender {
    if ([sender isKindOfClass:[SBDGroupChannel class]]) {
        sqlite3 *contactDB;
        char *dbpath = "<DATABASE_PATH>";
        char *query;
        sqlite3_stmt *stmt;

        if (sqlite3_open(dbpath, &contactDB) == SQLITE_OK) {
            query = "SELECT * FROM CHANNEL WHERE channel_url = ?";
            if (sqlite3_prepare_v2(contactDB, query, -1, &stmt, nil) == SQLITE_OK) {
                sqlite3_bind_text(stmt, 1, [sender.channelUrl UTF8String], -1, SQLITE_TRANSIENT); // channel_url
            }

            if (sqlite3_step(stmt) == SQLITE_ROW) {
                // If the channel is not currently cached, add it.
                sqlite3_finalize(stmt);

                const char *query = "INSERT INTO CHANNEL (channel_url, last_message_ts, payload) VALUES (?, ?, ?)";
                for (SBDGroupChannel *channel in channels) {
                    // Store each new channel into the local database
                    if (sqlite3_prepare_v2(contactDB, query, -1, &stmt, nil) == SQLITE_OK) {
                        sqlite3_bind_text(stmt, 1, [sender.channelUrl UTF8String], -1, SQLITE_TRANSIENT); // channel_url
                        sqlite3_bind_int64(stmt, 2, ((SBDGroupChannel *)sender).lastMessage.createdAt); // last_message_ts

                        NSData *blob = [sender serialize];
                        sqlite3_bind_blob(stmt, 3, [blob bytes], [blob length], SQLITE_TRANSIENT);
                    }    

                    if (sqlite3_step(stmt) != SQLITE_DONE) {
                        // Error!
                    }

                    sqlite3_finalize(stmt);
                }
            }
            else {
                // If the channel is in the current cache, update it.
                sqlite3_finalize(stmt);
                query = "UPDATE CHANNEL SET last_message_ts = ?, payload = ? WHERE channel_url = ?";
                if (sqlite3_prepare_v2(contactDB, query, -1, &stmt, nil) == SQLITE_OK) {
                    sqlite3_bind_int64(stmt, 1, ((SBDGroupChannel *)sender).lastMessage.createdAt); // last_message_ts

                    NSData *blob = [sender serialize];
                    sqlite3_bind_blob(stmt, 2, [blob bytes], [blob length], SQLITE_TRANSIENT);

                    sqlite3_bind_text(stmt, 3, [sender.channelUrl UTF8String], -1, SQLITE_TRANSIENT); // channel_url

                    if (sqlite3_step(stmt) != SQLITE_DONE) {
                        // Error!
                    }

                    sqlite3_finalize(stmt);
                }
            }
        }

        sqlite3_close(contactDB);
    }
}

A similar process can be followed for – channelWasDeleted:channelType:, – channel:userDidJoin:, and – channel:userDidLeave:.

Miscellaneous

This section contains information for the following:

  • Client error codes
  • Server error codes
  • Supported languages

Client error codes

The following errors that are defined by SBDErrorCode in SBDTypes.h are six-digit integers beginning with 800.

Error Code Description
SBDErrorInvalidInitialization 800100 Initialization failed
SBDErrorConnectionRequired 800101 Connection required
SBDErrorInvalidParameter 800110 Invalid parameters
SBDErrorNetworkError 800120 Network error
SBDErrorNetworkRoutingError 800121 Routing error
SBDErrorMalformedData 800130 Malformed data
SBDErrorMalformedErrorData 800140 Malformed error data
SBDErrorWrongChannelType 800150 Wrong channel type
SBDErrorMarkAsReadRateLimitExceeded 800160 Mark as read rate limit exceeded
SBDErrorQueryInProgress 800170 Query is in progress
SBDErrorAckTimeout 800180 Command ack timed out
SBDErrorLoginTimeout 800190 Login timed out
SBDErrorWebSocketConnectionClosed 800200 Connection closed
SBDErrorWebSocketConnectionFailed 800210 Connection failed
SBDErrorRequestFailed 800220 Request failed

Server error codes

The following errors are six-digit integers beginning with 400, 500, and 900.

Code Description
400100 Parameter Error - String value is required
400101 Parameter Error - Number value is required
400102 Parameter Error - List value is required
400103 Parameter Error - Json value is required
400104 Parameter Error - Boolean value is required
400105 Parameter Error - Not all the required fields are arrived
400106 Parameter Error - Value must be a positive number
400107 Parameter Error - Value must be a negative number
400108 User doesn't have an access to channels or messages
400110 Parameter Error - Length of value is not valid
400111 Parameter Error - Unknown
400112 Parameter Error - Should provide two different values
400151 Parameter Error - Not allowed characters
400201 Object(Channel/User/Message) not found
400202 Unique constraint violation
400300 User Authentication Error - Deactivated user
400301 User Authentication Error - Deleted user or user not found
400302 User Authentication Error - Invalid access token
400303 User Authentication Error - Unexpected error
400304 User Authentication Error - Application not found
400305 User Authentication Error - User id is too long
400306 User Authentication Error - Plan quota exceeded
400307 User Authentication Error - Requests from authorized domain
400601 The push token registration failure
400602 The push token removal failure
400910 Requests are rate-limited
400920 Tried to access non-allowed features under your plan
500901 Unexpected errors
900010 Try to send messages without login
900020 Try to send messages to group channels not belong to the user
900021 Try to send messages after getting deactivated
900030 Try to send messages to the channels when the guest policy is read-only on dashboard
900041 The user is muted on this channel
900050 User cannot send messages to frozen channels
900060 Message is blocked by profanity filter
900070 Try to send messages to deleted channels
900080 You cannot send messages on 1-on-1 group channel when receiver is blocked
900081 You cannot send messages on 1-on-1 group channel when receiver is deactivated
900100 Try to enter the banned channel
900200 You are blocked because you sent too many messages in short period

Supported Languages

SendBird provides message auto-translations for the languages listed in the following table.

Language Code Language Code
Afrikaans af Klingon (pIqaD) tlh-Qaak
Arabic ar Korean ko
Bosnian (Latin) bs-Latn Latvian lv
Bulgarian bg Lithuanian lt
Catalan ca Malay ms
Chinese Simplified zh-CHS Maltese mt
Chinese Traditional zh-CHT Norwegian no
Croatian hr Persian fa
Czech cs Polish pl
Danish da Portuguese pt
Dutch nl Querétaro Otomi otq
English en Romanian ro
Estonian et Russian ru
Finnish fi Serbian (Cyrillic) sr-Cyrl
French fr Serbian (Latin) sr-Latn
German de Slovak sk
Greek el Slovenian sl
Haitian Creole ht Spanish es
Hebrew he Swedish sv
Hindi hi Thai th
Hmong Daw mww Turkish tr
Hungarian hu Ukrainian uk
Indonesian id Urdu ur
Italian it Vietnamese vi
Japanese ja Welsh cy
Kiswahili sw Yucatec Maya yua
Klingon tlh -

Migration from v2

SDK 3.0 (v3) is a fully innovated chat solution for mobile apps and websites. The structure is elegant and the performance has increased dramatically.

  • OpenChannel and GroupChannel are added for Open Channel and Group Channel related features respectively.
  • UserMessage, FileMessage and AdminMessage are added to handle messages.
  • Callbacks and queries are neatly arranged.

We strongly encourage you in moving to v3 from the previous version and here are the migration tips.

Installation

If you are using CocoaPods, just change the version of dependencies in Podfile at app level (not project level).

Before
target 'YOUR_TARGET' do
  pod 'SendBirdSDK', '~> 2.2'
end
After
target 'YOUR_TARGET' do
  pod 'SendBirdSDK', '~> 3.0'
end

Authentication

Initialization

You still need to initialize SendBird once when your application begins.

Before
[SendBird initAppId:APP_ID];
After
[SBDMain initWithApplicationId:APP_ID];

Login

login is no longer used in v3. Calling connect just once after init is all you have to do.

Before
[SendBird loginWithUserId:USER_ID andUserName:USER_NAME]; // When you allow guest login.
[SendBird loginWithUserId:USER_ID andUserName:USER_NAME andUserImageUrl:USER_IMAGE_URL andAccessToken:ACCESS_TOKEN]; // When you allow only permitted user login.

[SendBird setEventHandlerConnectBlock:^(SendBirdChannel *channel) {
    // Connect handler block.
} errorBlock:^(NSInteger code) {

}
...

}];
After
// When you allow guest login.
[SBDMain connectWithUserId:self.userIdTextField.text completionHandler:^(SBDUser * _Nullable user, SBDError * _Nullable error) {

}];

// When you allow only permitted user login.
[SBDMain connectWithUserId:USER_ID accessToken:ACCESS_TOKEN completionHandler:^(SBDUser * _Nullable user, SBDError * _Nullable error) {

}];

If you want to update user information such as nickname, profile image or APNS push tokens, now you can use updateCurrentUserInfoWithNickname:profileUrl:completionHandler:, updateCurrentUserInfoWithNickname:profileImage:completionHandler: and registerDevicePushToken:completionHandler: after connection is established.

Open Channel (Previous Open Chat Channel)

From v3, we call Open Chat Channel as Open Channel. Members having entered an Open Channel are referred to as Participants of the channel.
SBDOpenChannel and SBDOpenChannelListQuery handle Open Channel related features.

Getting a List of Open Channels

Before
self.channelListQuery = [SendBird queryChannelList];
[self.channelListQuery nextWithResultBlock:^(NSMutableArray *queryResult) {

}];
After
self.openChannelListQuery = [SBDOpenChannel createOpenChannelListQuery];
[self.openChannelListQuery loadNextPageWithCompletionHandler:^(NSArray<SBDOpenChannel *> * _Nullable channels, SBDError * _Nullable error) {

}];

Connecting to an Open Channel

You don't have to do anything to connect to each Open Channel in v3. All required connections are automatically made once you have called connect after init.
Plus, entering an Open Channel is much simpler in v3. In v2, you needed to fetch channel URL and call join, queryMessageList and connect.
Now getChannel and enter is here for your convenience.

Before
[SendBird joinChannel:CHANNEL_URL];
[[SendBird queryMessageListInChannel:CHANNEL_URL prevWithMessageTs:LLONG_MAX andLimit:LIMIT resultBlock:^(NSMutableArray *queryResult) {
    if (queryResult != nil) {
        // Connect to SendBird with max messages timestamp to receive new messages since last query.
        [SendBird connectWithMessageTs:MAX_MESSAGE_TIMESTAMP];
    } 
}];
After
[SBDOpenChannel getChannelWithUrl:CHANNEL_URL completionHandler:^(SBDOpenChannel * _Nullable channel, SBDError * _Nullable error) {
    [channel enterChannelWithCompletionHandler:^(SBDError * _Nullable error) {

    }];
}];

Disconnecting an Open Channel

You don't have to do anything to disconnect an Open Channel in v3. All connections are automatically disconnected when you call disconnect on application termination.
If you want a user to leave an Open Channel, just call exit.

Before
SendBird.leave(CHANNEL_URL);
SendBird.disconnect();
After
[SBDOpenChannel getChannelWithUrl:CHANNEL_URL completionHandler:^(SBDOpenChannel * _Nullable channel, SBDError * _Nullable error) {
    [channel exitChannelWithCompletionHandler:^(SBDError * _Nullable error) {

    }];
}];

Sending Messages

Mentioned message is NOT currently supported in v3.

Before
[SendBird sendMessage:MESSAGE];
[SendBird sendMessage:MESSAGE withData:DATA];

[SendBird uploadFile:FILE type:TYPE hasSizeOfFile:SIZE withCustomField:CUSTOM_FIELD uploadBlock:^(SendBirdFileInfo *fileInfo, NSError *error) {
    [SendBird sendFile:fileInfo];
}];
After
[openChannel sendUserMessage:MESSAGE data:DATA completionHandler:^(SBDUserMessage * _Nullable userMessage, SBDError * _Nullable error) {

}];
[openChannel sendFileMessageWithBinaryData:FILE filename:FILE_NAME type:FILE_TYPE size:FILE_SIZE data:CUSTOM_DATA completionHandler:^(SBDFileMessage * _Nullable fileMessage, SBDError * _Nullable error) {

}];

Receiving Messages

SBDChannelDelegate replaces blocks of [SendBird setEventHandler...]. Multiple delegates are allowed.

Before
[SendBird setEventHandlerConnectBlock:^(SendBirdChannel *channel) {

} errorBlock:^(NSInteger code) {

} ...

}];
After
@interface ChattingViewController : UIViewController<SBDChannelDelegate>

@end

@implementation OpenChannelChattingViewController

- (void)initViewController {
    [SBDMain addChannelDelegate:self identifier:self.description];
}

- (void)dismissCurrentViewController {
    [SBDMain removeChannelDelegateForIdentifier:self.description];
}

- (void)channel:(SBDBaseChannel * _Nonnull)sender didReceiveMessage:(SBDBaseMessage * _Nonnull)message {

}

@end

Loading Previous Messages

Before
[[SendBird queryMessageListInChannel:CHANNEL_URL prevWithMessageTs:LLONG_MAX andLimit:LIMIT resultBlock:^(NSMutableArray *queryResult) {

}];
After
SBDMessageListQuery *query = [self.channel createMessageListQuery];
[query loadNextMessagesFromTimestamp:EARLIEST_MESSAGE_TIMESTAMP limit:LIMIT reverse:REVERSE_ORDER completionHandler:^(NSArray<SBDBaseMessage *> * _Nullable messages, SBDError * _Nullable error) {

}];

Getting a List of All Participants in an Open Channel

Participant means online users in a certain Open Channel. Once a user leaves the Open Channel, the user is no longer participant of that channel and query does not include him/her.

Before
SendBirdMemberListQuery *query = [SendBird queryMemberListInChannel:CHANNEL_URL];
[query nextWithResultBlock:^(NSMutableArray *queryResult) {

} errorBlock:^(NSError *error) {

}];
After
self.query = [self.channel createParticipantListQuery];
[self.query loadNextPageWithCompletionHandler:^(NSArray<SBDUser *> * _Nullable users, SBDError * _Nullable error) {

}];

Group Channel (Previous Messaging Channel)

From v3, we call Messaging Channel as Group Channel. Users having joined a Group Channel are referred to as Members of the channel.
SBDGroupChannel and SBDGroupChannelListQuery handle Group Channel related features.

Creating a Group Channel

All startMessaging related methods are replaced by createChannel and createChannelWithUserIds.

Before
[SendBird startMessagingWithUserIds:USER_IDS];
After
[SBDGroupChannel createChannelWithUserIds:USER_IDS isDistinct:YES/NO completionHandler:^(SBDGroupChannel * _Nullable channel, SBDError * _Nullable error) {

}];

Getting a List of Group Channels

Before
self.channelListQuery = [SendBird queryMessagingChannelList];
[self.channelListQuery nextWithResultBlock:^(NSMutableArray *queryResult) {

}];
After
self.groupChannelListQuery = [SBDGroupChannel createMyGroupChannelListQuery];
self.groupChannelListQuery.limit = LIMIT;
self.groupChannelListQuery.order = SBDGroupChannelListOrderChronological;
self.groupChannelListQuery.includeEmptyChannel = YES/OR;

[self.groupChannelListQuery loadNextPageWithCompletionHandler:^(NSArray<SBDGroupChannel *> * _Nullable channels, SBDError * _Nullable error) {

}];

Connecting to a Group Channel

You don't have to do anything to connect to each Group Channel in v3. All required connections are automatically made once you have called connect after init.

Before
[[SendBird queryMessageListInChannel:CHANNEL_URL] loadWithMessageTs:LLONG_MAX prevLimit:PREV_LIMIT andNextLimit:NEXT_LIMIT resultBlock:^(NSMutableArray *queryResult) {
    [SendBird joinChannel:CHANNEL_URL];
    [SendBird connectWithMessageTs:MAX_MESSAGE_TIMESTAMP];
} endBlock:^(NSError *error) {

}];
After
// Do nothing.

Disconnecting a Group Channel

You don't have to do anything to disconnect a Group Channel in v3. All connections are automatically disconnected when you call disconnect on application termination.

Before
[SendBird disconnect];
After
// Do nothing.

Inviting Users to an Existing Channel

Before
[SendBird inviteMessagingWithChannelUrl:CHANNEL_URL andUserIds:USER_ID];
After
[self.groupChannel inviteUserId:USER_IDS completionHandler:^(SBDError * _Nullable error) {

}];

Removing a User from Channel Members

Before
[SendBird endMessagingWithChannelUrl:CHANNEL_URL];
After
[channel leaveChannelWithCompletionHandler:^(SBDError * _Nullable error) {

}];

Sending Messages

Mentioned message is NOT currently supported in v3.

Before
[SendBird sendMessage:MESSAGE];
[SendBird sendMessage:MESSAGE withData:DATA];

[SendBird uploadFile:FILE type:TYPE hasSizeOfFile:SIZE withCustomField:CUSTOM_FIELD uploadBlock:^(SendBirdFileInfo *fileInfo, NSError *error) {
    [SendBird sendFile:fileInfo];
}];
After
[groupChannel sendUserMessage:MESSAGE data:DATA completionHandler:^(SBDUserMessage * _Nullable userMessage, SBDError * _Nullable error) {

}];
[groupChannel sendFileMessageWithBinaryData:FILE filename:FILE_NAME type:FILE_TYPE size:FILE_SIZE data:CUSTOM_DATA completionHandler:^(SBDFileMessage * _Nullable fileMessage, SBDError * _Nullable error) {

}];

Receiving Messages

Blocks of [SendBird setEventHandler...] replaces SBDChannelDelegate. Multiple delegates are allowed.

Before
[SendBird setEventHandlerConnectBlock:^(SendBirdChannel *channel) {

} errorBlock:^(NSInteger code) {

} ...

}];
After
@interface ChattingViewController : UIViewController<SBDChannelDelegate>

@end

@implementation OpenChannelChattingViewController

- (void)initViewController {
    [SBDMain addChannelDelegate:self identifier:self.description];
}

- (void)dismissCurrentViewController {
    [SBDMain removeChannelDelegateForIdentifier:self.description];
}

- (void)channel:(SBDBaseChannel * _Nonnull)sender didReceiveMessage:(SBDBaseMessage * _Nonnull)message {

}

@end

Loading Previous Messages

Before
[[SendBird queryMessageListInChannel:CHANNEL_URL prevWithMessageTs:LLONG_MAX andLimit:LIMIT resultBlock:^(NSMutableArray *queryResult) {

}];
After
SBDMessageListQuery *query = [self.channel createMessageListQuery];
[query loadNextMessagesFromTimestamp:EARLIEST_MESSAGE_TIMESTAMP limit:LIMIT reverse:REVERSE_ORDER completionHandler:^(NSArray<SBDBaseMessage *> * _Nullable messages, SBDError * _Nullable error) {

}];

Monitoring Multiple Channels

SBDChannelDelegate replaces blocks of [SendBird registerNotificationHandlerMessagingChannelUpdatedBlock:mentionUpdatedBlock:]. For details, please refer to Event Handler.

Before
[SendBird registerNotificationHandlerMessagingChannelUpdatedBlock:^(SendBirdMessagingChannel *channel) {

} mentionUpdatedBlock:^(SendBirdMention *mention) {

}];
After
@interface ChattingViewController : UIViewController<SBDChannelDelegate>

@end

@implementation OpenChannelChattingViewController

- (void)initViewController {
    [SBDMain addChannelDelegate:self identifier:self.description];
}

- (void)dismissCurrentViewController {
    [SBDMain removeChannelDelegateForIdentifier:self.description];
}
@end

Getting a List of All Members

Before
[messagingChannel members];
After
[messagingChannel members];

Typing Indicators

Before
[SendBird typeStart];
[SendBird typeEnd];
After
[groupChannel startTyping];
[groupChannel endTyping];

Getting Read Receipt

From v3, various of methods to get read receipt are possible. You can get a timestamp for a certain member just like in v2 or the automatically calculated read receipt as well.

Before
long timestamp = [messagingChannel getLastReadMillis:USER_ID];
After
long timestamp = [groupChannel getLastSeenAtByUserId:USER_ID];
int unreadCount = [channel getReadReceiptOfMessage:msg];

Broadcasting Read Status

Before
[SendBird markAsRead];
After
[groupChannel markAsRead];

Channel Metadata

Meta data and meta counter can be created, updated and read in v3 by counter parts of methods in v2.

Meta Data

Before
[SendBird queryChannelMetaDataWithChannelUrl:CHANNEL_URL] setMetaData:DATA_DICTIONARY resultBlock:^(NSDictionary *response) {

} endBlock:^(NSInteger code) {

}];
[SendBird queryChannelMetaDataWithChannelUrl:CHANNEL_URL] getMetaDataWithKeys:KEYS resultBlock:^(NSDictionary *response) {

} endBlock:^(NSInteger code) {

}];
After
[channel createMetaData:DATA_DICTIONARY completionHandler:^(NSDictionary<NSString *,NSString *> * _Nullable metaData, SBDError * _Nullable error) {

}];
[channel updateMetaData:DATA_DICTIONARY completionHandler:^(NSDictionary<NSString *,NSObject *> * _Nullable metaData, SBDError * _Nullable error) {

}];
[channel getMetaDataWithKeys:KEYS completionHandler:^(NSDictionary<NSString *,NSObject *> * _Nullable metaData, SBDError * _Nullable error) {

}];

Meta Counter

Before
[SendBird queryChannelMetaCounterWithChannelUrl:CHANNEL_URL] increaseMetaCounterWithKey:KEY andAmount:AMOUNT resultBlock:^(NSDictionary<NSString*,NSNumber*> *response) {

} endBlock:^(NSInteger code) {

}];
[SendBird queryChannelMetaCounterWithChannelUrl:CHANNEL_URL] getMetaCounterWithKeys:KEYS resultBlock:^(NSDictionary *response) {

} endBlock:^(NSInteger code) {

}];
After
[channel increaseMetaCounters:COUNTER_DICTIONARY completionHandler:^(NSDictionary<NSString *,NSNumber *> * _Nullable metaCounters, SBDError * _Nullable error) {

}];
[channel getMetaCountersWithKeys:KEYS completionHandler:^(NSDictionary<NSString *,NSNumber *> * _Nullable metaCounters, SBDError * _Nullable error) {

}];

Event Handler

SendBirdEventHandler, SendBirdSystemEventHandler and SendBirdNotificationHandler are replaced by SBDChannelDelegate.
Please refer to Event Handler for details.

Push Notifications

From v3, you have to call registerDevicePushToken:completionHandler: explicitly after connection is made to register push tokens.
Please refer to Push Notifications for details.

Before
// AppDelegate.m
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
    // We recommend to run the below code when the notification setting of your app is a default value or the setting is true.
    [SendBird registerForRemoteNotifications:devToken];
}
After
// AppDelegate.m
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
    // ...

    // Save devToken to your own global variable.
    [SBDMain registerDevicePushToken:tempPushToken completionHandler:^(SBDPushTokenRegistrationStatus status, SBDError * _Nullable error) {
        if (error == nil) {
            if (status == SBDPushTokenRegistrationStatusPending) {
                // Registration is pending.
                // If you get this status, invoke `+ registerDevicePushToken:completionHandler:` with `[SBDMain getPendingPushToken]` after connection.
            }
            else {
                // Registration succeeded.
            }
        }
        else {
            // Registration failed.
        }
    }];
}

// ViewController.m
[SBDMain connectWithUserId:self.userIdTextField.text completionHandler:^(SBDUser * _Nullable user, SBDError * _Nullable error) {
    [SBDMain registerDevicePushToken:[SBDMain getPendingPushToken] completionHandler:^(SBDPushTokenRegistrationStatus status, SBDError * _Nullable error) {
        if (error == nil) {
            if (status == SBDPushTokenRegistrationStatusPending) {
                NSLog(@"Push registration is pending.");
            }
            else {
                NSLog(@"APNS Token is registered.");
            }
        }
        else {
            NSLog(@"APNS registration failed.");
        }
    }];
}];

Change Log

v3.0.77(Dec 8, 2017)

  • Fixed state of SBDMember bug.

v3.0.75(Dec 8, 2017)

  • Added isActive property to SBDUser.

v3.0.74 (Nov 29, 2017)

  • Fixed bug.

v3.0.73 (Nov 13, 2017)

  • Added channelWasHidden: delegate to SBDChannelDelegate for a channel hidden event.

v3.0.72 (Nov 12, 2017)

  • Added a group channel hidden event.

v3.0.71 (Nov 7, 2017)

  • Added isHidden property to a group channel.

v3.0.70 (Oct 24, 2017)

  • Fixed bug of file message's custom type.

v3.0.69 (Oct 18, 2017)

  • Turned off LLVM profiling and code coverage options.

v3.0.68 (Oct 11, 2017)

  • Bitcode support.

v3.0.67 (Oct 6, 2017)

  • DO NOT USE THIS VERSION
  • Fixed reconnection bug.

v3.0.66 (Sep 7, 2017)

  • Fixed message parsing bug.

v3.0.65 (Aug 28, 2017)

  • Added a method to count my group channels.
  • Added a method to reset the group channel history.

v3.0.64 (Aug 21, 2017)

  • Added user block flags to a member of group channels.
  • Added a method to get message changelogs.
  • Deprecated filters of channel list query as properties.
  • Added methods for filters of channel list query.

v3.0.63 (Aug 18, 2017)

  • Added new option to the method to hide a group channel for hiding the previous messages when the channel was hidden.
  • Added freeze state to open channel.
  • Added user meta data.
  • Added meta data filter to user list query.

v3.0.62 (Aug 11, 2017)

  • Set default value to channel data.
  • Added conditions to avoid null value when channel instance is updated.
  • Fixed URL encoding bug.
  • Fixed memory leak.
  • Added events for changing meta counters and meta data.

v3.0.61 (Jul 25, 2017)

  • Added channel url filter for group channel list query.

v3.0.60 (Jul 19, 2017)

  • Fixed serialization bug.

v3.0.59 (Jul 17, 2017)

  • Fixed serialization bug.

v3.0.58 (Jul 15, 2017)

  • Added methods to copy a user message and a file message to another channel.

v3.0.57 (Jul 10, 2017)

  • Added SBDMember class for an invitation.
  • Changed member objects of SBDGroupChannel to SBDMember objects.
  • Added the methods to accept and decline an invitation to SBDGroupChannel object.
  • Added SBDMemberStateFilter for group channel list query.
  • Added the methods to set and get the channel invitation preference for automatic acceptance.
  • Added channel:userDidJoin:inviter: delegate for invitation acceptance.
  • Added channel:didReceiveInvitation:inviter: delegate for an invitation.

v3.0.56 (Jul 4, 2017)

  • Added a setter and a getter for push sound.

v3.0.55 (Jun 14, 2017)

  • Added custom type filter to channel query.

v3.0.54 (Jun 5, 2017)

  • Fixed bug of participant count in open channel.
  • Added a channel URL parameter to a method to create an open channel.

v3.0.53 (May 19, 2017)

  • Added methods to update a user message and a file message.
  • Added a delegate to receive the event for message updating.
  • Added a method to cancel uploading file message.

v3.0.52 (Apr 24, 2017)

  • Improved connection performance.

v3.0.51 (Apr 19, 2017)

  • Only iOS 8.0 or later is supported from this version. Podfile has to be updated for dynamic frameworks like this:
platform :ios, '8.0'

target YOUR_PROJECT_TARGET do
  use_frameworks!

  pod 'SendBirdSDK'
end

v3.0.50 (Apr 14, 2017)

  • This is the biggest release since our initial v3 release. Some of changes should be carefully taken when you update the SDK.
  • Now every completion handler/delegates in SDK is returned to MAIN THREAD queue. It used to be inconsistent so some were returned to main thread and others to background thread. Be aware that you shouldn't call any blocking/long running job without using custom background queue in our completion handler/delegates!!!!
    For your convenience we added the methods to set default dispatch queue for every delegates and completion handlers. The queue can be changed like this:
    [SBDMain setCompletionHandlerDelegateQueue:dispatch_get_main_queue()];
    
    or
    [SBDMain setCompletionHandlerDelegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
    
  • The auto reconnection is also triggered with network awareness. The default option is on but you might want to turn it off if you have manual network detection to call reconnect(). It can be turned on or off like this:
    [SBDMain setNetworkAwarenessReconnection:YES];
    
    or
    [SBDMain setNetworkAwarenessReconnection:NO];
    
  • Now we support file uploading in background so you can send very large files.

v3.0.45 (Apr 8, 2017)

  • Improved connection speed.

v3.0.44 (Mar 13, 2017)

  • Fixed serialization bug.

v3.0.43 (Mar 9, 2017)

  • Fixed unread message count bug.

v3.0.42 (Mar 8, 2017)

  • Fixed unread message count sync problem.
  • Added a method to get total unread channel count.

v3.0.41 (Mar 6, 2017)

  • Added option for sync member in group channel and sender of message.
  • Added accurate error.
  • Fixed unread count bug of group channel.
  • Added message type and custom type options for getting messages.

v3.0.40 (Mar 2, 2017)

  • Added serialization for SBDUser, SBDBaseChannel, and SBDBaseMessage object.

v3.0.39 (Feb 25, 2017)

  • Fixed bugs of send user message and file message.

v3.0.38 (Feb 17, 2017)

  • Added file encryption.
  • Fixed connection bug.

v3.0.37 (Feb 14, 2017)

  • Fixed bug of updating meta counter of channel.

Note that we don't return result objects anymore when there's an error in completion handlers starting from this version. Please make sure that you don't use a result object when an error occurs after updating SDK.

v3.0.36 (Jan 31, 2017)

  • Fixed bug of thumbnail image in file message.

v3.0.35 (Jan 30, 2017)

  • Added a method to run reconnection.
  • Improved unread message count management.
  • Added error for uninitialized class.

v3.0.33 (Jan 28, 2017)

  • Improved usability of channel delegates and connection delegates.

v3.0.32 (Jan 20, 2017)

  • Modified network error code.

v3.0.31 (Jan 19, 2017)

  • Added error code for network error.

v3.0.30 (Jan 18, 2017)

  • More accurate connection state.

v3.0.29 (Jan 17, 2017)

  • Fixed the bug of unread message count in group channel.
  • Added file name parameter to the message for sending file with URL.

v3.0.28 (Jan 9, 2017)

  • Added file path parameter to the methods which are for uploading file.

v3.0.27 (Jan 4, 2017)

  • Added the class to represent a thumbnail max size.

v3.0.26 (Jan 3, 2017)

  • Fixed the bug of the group channel's data.
  • Added the feature for generating the thumbnail of the image file message.

v3.0.25 (Dec 23, 2016)

  • Added a feature that sets a push notification template.

v3.0.24 (Dec 19, 2016)

  • Fixed channel URL and user ID encoding bug.
  • Fixed bug of creating group channel with distinct option.

v3.0.22 (Dec 16, 2016)

  • Added unique option to push registration.

v3.0.21 (Dec 6, 2016)

  • Fixed bug of unread message count of group channel.
  • Added isPushEnabled property to SBDGroupChannel in order to represent the push notification configuration.
  • Deprecated getPushPreferenceWithCompletionHandler: of SBDGroupChannel.

v3.0.20 (Dec 2, 2016)

  • Fixed bug of unread message count and last message of group channel.

v3.0.19 (Dec 1, 2016)

  • Added user IDs filters and query type to SBDGroupChannelListQuery.
  • Added channel custom type for SBDOpenChannel and SBDGroupChannel.
  • Fixed to call channelWasChanged: delegate when unread message count or last message has been updated.
  • Fixed bug of reconnection for SBDOpenChannel.

v3.0.18 (Nov 23, 2016)

  • Fixed bug of last message of group channels.

v3.0.17 (Nov 4, 2016)

  • Fixed connection bug.

v3.0.16 (Nov 3, 2016)

  • Added group channel list search by a member nickname. (Search by multiple nicknames option in v3.0.15 is no more supported.)
  • Added auto-translating feature to SBDUserMessage.
  • Improved connection performance.

v3.0.15 (Oct 31, 2016)

  • Added custom type to messages.
  • Added group channel list search by member nicknames and user IDs.
  • Added creating and updating channel cover image with binary file.

v3.0.14 (Oct 11, 2016)

  • Fixed URL encoding bug.
  • Fixed minor bug.

v3.0.13 (Oct 4, 2016)

  • Support Base SDK 9.3
  • Fixed bug of the channel type of messages.

v3.0.12 (Sep 30, 2016)

  • Support Carthage.
  • Fixed unread message count bug.
  • Fixed minor bugs.

v3.0.11 (Sep 23, 2016)

  • Fixed bug of disconnection and reconnection(APNS-related bug).

v3.0.10 (Sep 13, 2016)

  • Removed validation of sending a user message.
  • Added a channel type to messages from a message query.

v3.0.9 (Sep 6, 2016)

  • Fixed bug of data of SBDFileMessage.
  • Added a method to get user list with user IDs(+ createUserListQueryWithUserIds: of SBDMain).
  • Added a method to register a APNS device token without login(+ registerDevicePushToken:completionHandler: of SBDMain).
  • Added keywords for open channel name and URL search(urlKeyword and nameKeyword of SBDOpenChannelListQuery).
  • Added a method to get a total unread message count(+ getTotalUnreadMessageCountWithCompletionHandler: of SBDGroupChannel).
  • Added a method to get a last timestamp when a user read at the channel(- getLastSeenAtByUser: and - getLastSeenAtByUserId: of SBDGroupChannel).
  • Added a method to get members who read the message(- getReadMembersWithMessage: of SBDGroupChannel).
  • Added a method to get members who did not read the message(- getUnreadMembersWithMessage: of SBDGroupChannel).
  • Added a method to get the read status(- getReadStatus of SBDGroupChannel).
  • Added methods to set push preference(- setPushPreferenceWithPushOn:completionHandler: and - getPushPreferenceWithCompletionHandler: of SBDGroupChannel. + setDoNotDisturbWithEnable:startHour:startMin:endHour:endMin:timezone:completionHandler: and + getDoNotDisturbWithCompletionHandler: of SBDMain).
  • Deprecated + registerPushToken:completionHandler: of SBDMain.

v3.0.8 (Sep 1, 2016)

  • Fixed group channel unread count bug.

v3.0.7 (Sep 1, 2016)

  • Fixed minor bug.

v3.0.6 (Sep 1, 2016)

  • Added URL encoding to API parameters.

v3.0.5 (Sep 1, 2016)

  • DO NOT USE THIS VERSION

v3.0.4 (Aug 30, 2016)

  • Removed duplicated symbols.

v3.0.3 (Aug 30, 2016)

  • Removed duplicated symbol.

v3.0.2 (Aug 26, 2016)

  • Updated version.

v3.0.1 (Aug 25, 2016)

  • Fixed reconnection bug.

v3.0.0 (Aug 12, 2016)

  • First release.