Group Channel: Advanced

This section explains the advanced features of Group Channel. Some of them are the premium features available only to a paying user.


Add users of an application as friends

To find and chat with other users within the application easily, users can add friends by using unique identifiers such as hashed phone numbers for privacy. When the unique identifier, which is used as the discovering keys, linked to a user using our service is uploaded, we can match and retrieve the users and then automatically register them as friends.

//In case of uploading friend discovery keys
NSMutableDictionary<NSString *, NSString *> *discoveryMap = [[NSMutableDictionary alloc] init];
discoveryMap[@"0001-0002-0003"] = @"John";
discoveryMap[@"0004-0005-0006"] = @"Jay";
discoveryMap[@"0007-0008-0009"] = @"Jin";

[SBDMain uploadFriendDiscoveries:discoveryMap completionHandler:^(SBDError * _Nullable error) {
    if (error != nil) {    // Error.
        return;
    }
}];

//In case of deleting friend discovery keys
NSMutableArray<NSString *> *discoveryKeys = [[NSMutableArray alloc] init];
[discoveryKeys addObject:@"0001-0002-0003"];
[discoveryKeys addObject:@"0004-0005-0006"];
[discoveryKeys addObject:@"0007-0008-0009"];

[SBDMain deleteFriendsWithDiscoveries:discoveryKeys completionHandler:^(SBDError * _Nullable error) {
    if (error != nil) {    // Error.
        return;
    }
}];
//In case of uploading friend discovery keys
var discoveryMap:[String:String] = [:]
discoveryMap["0001-0002-0003"] = "John"
discoveryMap["0004-0005-0006"] = "Jay"
discoveryMap["0007-0008-0009"] = "Jin"

SBDMain.uploadFriendDiscoveries(discoveryMap) { (error) in
    guard error == nil else {    // Error.
        return
    }
}

//In case of deleting friend discovery keys
var discoveryKeys:[String] = []
discoveryKeys.append("0001-0002-0003")
discoveryKeys.append("0004-0005-0006")
discoveryKeys.append("0007-0008-0009")

SBDMain.deleteFriends(withDiscoveries: discoveryKeys) { (error) in
    guard error == nil else {    // Error.
        return
    }
}

Or you can make any user add and delete friends by using the userIds of others like below.

NSMutableArray<NSString *> *userIds = [[NSMutableArray alloc] init];
[userIds addObject:@"Harry"];

// In case of adding friends 
[SBDMain addFriendsWithUserIds:userIds completionHandler:^(NSArray<SBDUser *> * _Nullable users, SBDError * _Nullable error) {
    if (error != nil) {    // Error.
        return;
    }
}];

// In case of deleting friends 
[SBDMain deleteFriendsWithUserIds:userIds completionHandler:^(SBDError * _Nullable error) {
    if (error != nil) {    // Error.
        return;
    }
}];
var userIds:[String] = []
userIds.append("Harry")

// In case of adding friends 
SBDMain.addFriends(withUserIds: userIds) { (users, error) in
    guard error == nil else {    // Error.
        return
    }
}

// In case of deleting friends 
SBDMain.deleteFriends(withUserIds: userIds) { (error) in
    guard error == nil else {    // Error.
        return
    }
}

Also you can retrieve a list of friends of a certain user by creating a SBDFriendListQuery instance. Its loadNextPageWithCompletionHandler: method returns a list of SBDUser objects.

SBDFriendListQuery *friendListQuery = [SBDMain createFriendListQuery];
[friendListQuery loadNextPageWithCompletionHandler:^(NSArray<SBDUser *> * _Nullable users, SBDError * _Nullable error) {
    if (error != nil) {    // Error.
        return;
    }
}];
let friendListQuery = SBDMain.createFriendListQuery()
friendListQuery?.loadNextPage(completionHandler: { (users, error) in
    guard error == nil else {    // Error.
        return
    }
})

Send typing indicators to other members in a group channel

Calling startTyping and endTyping when a user is typing will invoke channelDidUpdateTypingStatus: callback in the channel delegate.

[groupChannel startTyping];
[groupChannel endTyping];

// ...

@interface GroupChannelViewController : ViewController<SBDChannelDelegate>

@end

// ...

[SBDMain addChannelDelegate:self identifier:UNIQUE_DELEGATE_ID];

// ...

- (void)channelDidUpdateTypingStatus:(SBDGroupChannel * _Nonnull)sender {
    if ([sender.channelUrl isEqualToString:self.channel.channelUrl]) {
        NSArray<SBDUser *> *members = [sender getTypingMembers];

        // Refresh typing status of members within channel.
        }
}
self.groupChannel.startTyping()
self.groupChannel.endTyping()

// ...

class GroupChannelChattingViewController: UIViewController, SBDChannelDelegate {

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

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

            // Refresh typing status of members within channel.
        }
    }
}

Mark messages as read in a group channel

Calling markAsRead when a member reads messages will invoke channelDidUpdateReadReceipt: callback in the channel delegate of all members within and then make the channel up-to-date.

[channel markAsRead];

// ...

@interface GroupChannelViewController : ViewController<SBDChannelDelegate>

@end

// ...

[SBDMain addChannelDelegate:self identifier:UNIQUE_DELEGATE_ID];

// ...

- (void)channelDidUpdateReadReceipt:(SBDGroupChannel * _Nonnull)sender {

}
channel?.markAsRead()

// ...

class GroupChannelChattingViewController: UIViewController, SBDChannelDelegate {

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

    // ...
    func channelDidUpdateReadReceipt(_ sender: SBDGroupChannel) {

    }
}

Retrieve the read receipt of a message in a group channel

You can check a number of members in a group channel who have not read a message using getReadReceiptOfMessage:. To get the exact value, the channel should be updated by markAsRead before calling the getReadReceiptOfMessage:.

[channel markAsRead];

// ...

@interface GroupChannelViewController : ViewController<SBDChannelDelegate>

@end

// ...

[SBDMain addChannelDelegate:self identifier:UNIQUE_DELEAGATE_ID];

// ...

- (void)channelDidUpdateReadReceipt:(SBDGroupChannel * _Nonnull)sender {

    // ...
    if ([currentGroupChannel.channelUrl isEqualToString:sender.channelUrl]) {

        // ...
        for (SBDBaseMessage *msg in messages) {
            int unreadCount = [sender getReadReceiptOfMessage:msg];
            if (unreadCount <= 0) {
                // All members have read the message.
            }
            else {
                // Some of members haven't read the message.            
            }
        }
    }
}
channel?.markAsRead()

// ...

class GroupChannelChattingViewController: UIViewController, SBDChannelDelegate {

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

    // ...
    func channelDidUpdateReadReceipt(_ sender: SBDGroupChannel) {
        if currentGroupChannel?.channelUrl == sender.channelUrl {

            // ...
            for msg in messages {
                let unreadCount = sender.getReadReceipt(of: msg)
                if unreadCount <= 0 {
                    // All members have read the message.
                }
                else {
                    // Some of members haven't read the message.
                }
            }
        }
    }
}

Retrieve members who have read a message

Using getReadMembersWithMessage: method, you can view members who have read a message in a group channel. A list of the members for each message is depending on each member's read receipt. For keeping track of who have read a new message, you should pass a newly received message as a parameter to getReadMembersWithMessage: within channelDidUpdateReadReceipt: callback which is called when other member has read a message.

NSArray *readMembers = [self.channel getReadMembersWithMessage:baseMessage];
let readMembers = self.groupChannel.getReadMembers(with: baseMessage)

The getReadMembersWithMessage: returns a list of members who have read a message except the current user and the message sender.

Note: Similarly, using getUnreadMembersWithMessage: method, you can retreive a list of members who have not read a message except the current user and the message sender. And getReadStatus method returns a list of all members' user information except the current user.


Retrieve the last message of a group channel

You can view the last message of a group channel.

SBDBaseMessage *lastMessage = channel.lastMessage;
let lastMessage = channel.lastMessage

Retrieve count of unread messages in a group channel

You can retrieve count of unread messages of a certain user in a group channel.

NSUInteger unreadMessageCount = channel.unreadMessageCount;
let unreadMessageCount = channel.unreadMessageCount

Retrieve total count of unread messages in group channels

You can retrieve total count of unread messages of a certain user in all joined group channels.

[SBDGroupChannel getTotalUnreadMessageCountWithCompletionHandler:^(NSUInteger unreadCount, SBDError * _Nullable error) {
    if (error != nil) {    // Error.
        return;
    }
}];
SBDGroupChannel.getTotalUnreadMessageCount { (totalUnreadMessageCount, error) in
    guard error == nil else {    // Error.
        return
    }
}

Retrieve total count of group channels which have unread messages

You can retrieve total count of group channels which have unread messages of a certain user.

[SBDGroupChannel getTotalUnreadChannelCountWithCompletionHandler:^(NSUInteger unreadCount, SBDError * _Nullable error) {
    if (error != nil) {    // Error.
        return;
    }
}];
SBDGroupChannel.getTotalUnreadChannelCount { (totalUnreadChannelCount, error) in
    guard error == nil else {    // Error.
        return
    }
}

Send admin messages to a group channel

You can send admin messages to users in a channel using the SendBird Dashboard or the Platform API. To send an admin message via the Dashboard, in the Group Channels panel, select a group channel, find the message box below, click the Admin Message tab, and write the message in the box. An admin message is limited to 1000 characters.

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


Categorize group channels by custom types

When creating a group 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.

Note: 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 (for example, distinguishing School and Work channels). However, both these fields can be flexibly utilized.

SBDGroupChannelParams *params = [[SBDGroupChannelParams alloc] init];
params.name = @"NAME";
params.coverUrl = @"COVER_URL";
params.data = @"DATA";
params.customType = @"CUSTOM_TYPE";
[SBDGroupChannel createChannelWithParams:params completionHandler:^(SBDGroupChannel * _Nullable channel, SBDError * _Nullable error) {
    if (error != nil) {    // Error.
        return;
    }
}];
var params = SBDGroupChannelParams()
params.name = "NAME"
params.coverUrl = "COVER_URL"
params.data = "DATA"
params.customType = "CUSTOM_TYPE"
SBDGroupChannel.createChannel(with: params) { (channel, error) in
    guard error == nil else {    // Error.
        return
    }
}

To get a channel's custom type, read channel.customType.


Categorize messages in a group channel by custom types

By specifying a custom type for messages, you can 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.

Note: 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 (for example, distinguishing FILE_IMAGE and FILE_AUDIO type messages). However, both these fields can be flexibly utilized.

To embed a custom type into a message, pass a String parameter to sendUserMessage: or sendFileMessage: method.

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

To get a message's custom type, read message.customType.


Search for group channels by name and URL

You can search for specific channels by adding keywords to a SBDGroupChannelListQuery instance. There are two types of keywords: a Name and a URL. A query returns a list of group channels that partially match the specified Name keyword in the names of the channels.

myChannelListQuery = [SBDGroupChannel createMyGroupChannelListQuery];
myChannelListQuery.includeEmptyChannel = YES;
[myChannelListQuery setChannelNameContainsFilter:@"SendBird"];
[myChannelListQuery loadNextPageWithCompletionHandler:^(NSArray<SBDGroupChannel *> * _Nullable channels, SBDError * _Nullable error) {
    if (error != nil) {    // Error.
        return;
    }

    // A list of group channels that partially match "SendBird" in their names is returned.
}];
myChannelListQuery = SBDGroupChannel.createMyGroupChannelListQuery()
myChannelListQuery?.includeEmptyChannel = true
myChannelListQuery?.setChannelNameContainsFilter("SendBird")
myChannelListQuery?.loadNextPage(completionHandler: { (channels, error) in
    guard error == nil else {    // Error.
        return
    }

    // A list of group channels that partially match "SendBird" in their names is returned.
})

A SBDGroupChannelListQuery instance returns a list of group channels that partially match the specified URL keyword in the urls of the channels.

SBDGroupChannelListQuery *myChannelListQuery = [SBDGroupChannel createMyGroupChannelListQuery];
myChannelListQuery.includeEmptyChannel = YES;

NSMutableArray<NSString *> *channelUrls = [[NSMutableArray alloc] init];
[channelUrls addObject:@"seminar"];
[channelUrls addObject:@"lecture"];

[myChannelListQuery setChannelUrlsFilter:channelUrls];

[myChannelListQuery loadNextPageWithCompletionHandler:^(NSArray<SBDGroupChannel *> * _Nullable channels, SBDError * _Nullable error) {
    if (error != nil) {    // Error.
        return;
    }

    // A list of group channels that partially match "seminar" or "lecture" in their names is returned.

}];
var myChannelListQuery = SBDGroupChannel.createMyGroupChannelListQuery()
myChannelListQuery?.includeEmptyChannel = true

var channelUrls: [String] = []
channelUrls.append("seminar")
channelUrls.append("lecture")

myChannelListQuery?.channelUrlsFilter = channelUrls

myChannelListQuery?.loadNextPage(completionHandler: { (channels, error) in
    guard error == nil else {    // Error.
        return
    }

    // A list of group channels that partially match "seminar" or "lecture" in their names is returned.
})

Generate thumbnails of a file message

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.

Note: Supported file types are files whose media type is image/* or video/*. The SDK does not support creating thumbnails when sending a file message with a file URL.

Create a NSArray of SBDThumbnailSize objects to pass to sendFileMessageWithBinaryData:filename:type:size: ... :progressHandler:completionHandler: method of SBDBaseChannel instance. A SBDThumbnailSize can be created with the constructor SBDThumbnailSize makeWithMaxCGSize: or SBDThumbnailSize makeWithMaxWidth:maxHeight:, where the values specify pixels. The completionHandler callback subsequently returns a NSArray of SBDThumbnail objects that each contain the URL of the generated thumbnail image file.

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:CUSTOM_TYPE 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;    // 100
    CGSize maxSizeSecond = second.maxSize;  // 200

    NSString *urlFirst = first.url;
    NSString *urlSecond = second.url;
}];
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: CUSTOM_TYPE, progressHandler: nil) { (fileMessage, error) in
    guard error == nil else {       // Error.
        return
    }

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

    let maxSizeFirst = first?.maxSize       // 100
    let maxSizeSecond = second?.maxSize     // 200

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

maxWidth and maxHeight specify the maximum dimensions of the thumbnail. Your image is resized evenly to fit within the bounds of (maxWidth, maxHeight). Note that if the original image is smaller than the specified dimensions, the thumbnail isn't resized. url returns the location of the generated thumbnail file within the SendBird servers.

As this is one of SendBird's Premium Features, please contact our sales team for further assistance.


Share an encrypted file with other participants

This feature encrypts all file messages sent by a channel participant with a master key when saved on SendBird server. This encrypted file has its own access URL, and only participants with the master key can decrypt the file and open it.

As this is one of SendBird's Premium Features, please contact our sales team for further assistance. This feature will also require customized implementation efforts from your side.


Spam flood protection

This feature allows you to customize the number of messages a member can send in a group channel per second. By doing so, all excess messages will be deleted and only the number of messages allowed to be sent per member per second will be delivered. This feature protects your application from some members spamming others in the channel with the same messages.

Note: Our default system setting is 5 messages per second. This limit can be manually adjusted only by our side. You can contact our engineering team for further assistance on this setting. As this is one of SendBird's Premium Features, please contact our sales team for further assistance.


Message auto-translation

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: method to request translated messages in the corresponding languages.

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

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

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

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

    // Display translation in UI.
}

Note: The message auto-translation supports 53 languages. For the language code table, see the Miscellaneous > Supported Languages. As this is one of SendBird's Premium Features, please contact our sales team for further assistance.