Implement Open Chat

Create Channels on SendBird Dashboard

You have to create channels in order to implement open chat in your application.

  1. Sign in at SendBird Dashboard.
  2. Select APPLICATION on the side menu and click on your app.
  3. Click CHANNELS tab.
  4. Click CREATE CHANNEL button.
  5. Fill up the form to create the channel you want.
  6. Click CREATE button.

Initialize SendBird Framework

Open the sample project and AppDelegate.m in Xcode.

AppDelegate.m

Initialize SendBird framework in application:didFinishLaunchingWithOptions:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    [SendBird initAppId:@"<YOUR_APP_ID>"];

    return YES;
}

Implement Channel Loading

Open OpenChatChannelListViewController.m in Xcode.

OpenChatChannelListViewController.m

Add SendBirdChannelListQuery object for querying the channels.

@interface OpenChatChannelListViewController ()<UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate> {
    NSMutableArray *channelArray;
    BOOL isLoadingChannel;
    SendBirdChannelListQuery *channelListQuery;
}

Insert the following code to log in and query the channels at the bottom of viewDidLoad method.

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    isLoadingChannel = NO;

    [self.openChatChannelListTableView setContentInset:UIEdgeInsetsMake(108, 0, 48, 0)];
    [self.openChatChannelListTableView setDelegate:self];
    [self.openChatChannelListTableView setDataSource:self];

    [self.channelSearchBar setDelegate:self];

    channelArray = [[NSMutableArray alloc] init];

    [self.openChatChannelListLoadingIndicator setHidden:YES];

    [SendBird loginWithUserId:[SendBird deviceUniqueID] andUserName:[MyUtils getUserName] andUserImageUrl:[MyUtils getUserProfileImage] andAccessToken:@""];
    channelListQuery = [SendBird queryChannelList];
    [channelListQuery nextWithResultBlock:^(NSMutableArray *queryResult) {
        for (SendBirdChannel *channel in queryResult) {
            [channelArray addObject:channel];
        }
        [self.openChatChannelListTableView reloadData];
    } endBlock:^(NSError *error) {

    }];
}

User ID is used to identify users in your application. This project uses device ID(IDFV) as User ID. If there isn’t the user which has the user ID, SendBird will generate one.

Channel queries support pagination. Let’s try it out.

The loadNextChannelList method will be invoked when the UITableView for channels draws the last row. If you scroll to the bottom of the UITableView, the next page of channels will be fetched from SendBird server and attached at the bottom of the UITableView.

Modify loadNextChannelList like this:

- (void)loadNextChannelList
{    
    if (![channelListQuery hasNext]) {
        return;
    }

    if (isLoadingChannel) {
        return;
    }
    isLoadingChannel = YES;

    [channelListQuery nextWithResultBlock:^(NSMutableArray *queryResult) {
        for (SendBirdChannel *channel in queryResult) {
            [channelArray addObject:channel];
        }
        [self.openChatChannelListTableView reloadData];
        isLoadingChannel = NO;
    } endBlock:^(NSError *error) {

    }];
}

SendBird supports channel search which is implemented at UISearchBar in the sample project.

Modify searchBarSearchButtonClicked: to query with a keyword and searchBarCancelButtonClicked: to clear the result.

- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
    [channelArray removeAllObjects];
    [searchBar setText:@""];
    channelListQuery = [SendBird queryChannelList];
    [channelListQuery setQuery:@""];
    [channelListQuery nextWithResultBlock:^(NSMutableArray *queryResult) {
        for (SendBirdChannel *channel in queryResult) {
            [channelArray addObject:channel];
        }
        [self.openChatChannelListTableView reloadData];
    } endBlock:^(NSError *error) {

    }];
}

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
    [channelArray removeAllObjects];
    channelListQuery = [SendBird queryChannelList];
    [channelListQuery setQuery:[searchBar text]];
    [channelListQuery nextWithResultBlock:^(NSMutableArray *queryResult) {
        for (SendBirdChannel *channel in queryResult) {
            [channelArray addObject:channel];
        }
        [self.openChatChannelListTableView reloadData];
    } endBlock:^(NSError *error) {

    }];
}

If you run the sample project, you can see the channels you created on the SendBird Dashboard.

Channel List Channel List Querying

Transfer a Message in Open Chat

Open OpenChatChattingViewController.m in Xcode.

Open OpenChatChattingViewController.m

The user interface for the chat consists of UITableView for displaying messages, UIButton for sending an image, UITextField for entering a message, and UIButton for sending a message.

Modify startChattingWithPreviousMessage: method. This method is invoked when the chat begins. If you want to get previous messages during initialization, pass YES as an argument. Otherwise, pass NO.

- (void)startChattingWithPreviousMessage:(BOOL)tf
{
    [SendBird loginWithUserId:[SendBird deviceUniqueID] andUserName:[MyUtils getUserName] andUserImageUrl:[MyUtils getUserProfileImage] andAccessToken:@""];
    [SendBird joinChannel:[currentChannel url]];
    [SendBird setEventHandlerConnectBlock:^(SendBirdChannel *channel) {

    } errorBlock:^(NSInteger code) {

    } channelLeftBlock:^(SendBirdChannel *channel) {

    } messageReceivedBlock:^(SendBirdMessage *message) {
        if (lastMessageTimestamp < [message getMessageTimestamp]) {
            lastMessageTimestamp = [message getMessageTimestamp];
        }

        if (firstMessageTimestamp > [message getMessageTimestamp]) {
            firstMessageTimestamp = [message getMessageTimestamp];
        }

        if ([message isPast]) {
            [messages insertObject:message atIndex:0];
        }
        else {
            [messages addObject:message];
        }
        [self scrollToBottomWithReloading:YES animated:NO];
    } systemMessageReceivedBlock:^(SendBirdSystemMessage *message) {

    } broadcastMessageReceivedBlock:^(SendBirdBroadcastMessage *message) {
        if (lastMessageTimestamp < [message getMessageTimestamp]) {
            lastMessageTimestamp = [message getMessageTimestamp];
        }

        if (firstMessageTimestamp > [message getMessageTimestamp]) {
            firstMessageTimestamp = [message getMessageTimestamp];
        }

        if ([message isPast]) {
            [messages insertObject:message atIndex:0];
        }
        else {
            [messages addObject:message];
        }
        [self scrollToBottomWithReloading:YES animated:NO];
    } fileReceivedBlock:^(SendBirdFileLink *fileLink) {
        if (lastMessageTimestamp < [fileLink getMessageTimestamp]) {
            lastMessageTimestamp = [fileLink getMessageTimestamp];
        }

        if (firstMessageTimestamp > [fileLink getMessageTimestamp]) {
            firstMessageTimestamp = [fileLink getMessageTimestamp];
        }

        if ([fileLink isPast]) {
            [messages insertObject:fileLink atIndex:0];
        }
        else {
            [messages addObject:fileLink];
        }
        [self scrollToBottomWithReloading:YES animated:NO];
    } messagingStartedBlock:^(SendBirdMessagingChannel *channel) {
        UIStoryboard *storyboard = [self storyboard];
        MessagingViewController *vc = [storyboard instantiateViewControllerWithIdentifier:@"MessagingViewController"];
        [vc setMessagingChannel:channel];
        [vc setDelegate:self];
        [self presentViewController:vc animated:YES completion:nil];
    } messagingUpdatedBlock:^(SendBirdMessagingChannel *channel) {

    } messagingEndedBlock:^(SendBirdMessagingChannel *channel) {

    } allMessagingEndedBlock:^{

    } messagingHiddenBlock:^(SendBirdMessagingChannel *channel) {

    } allMessagingHiddenBlock:^{

    } readReceivedBlock:^(SendBirdReadStatus *status) {

    } typeStartReceivedBlock:^(SendBirdTypeStatus *status) {

    } typeEndReceivedBlock:^(SendBirdTypeStatus *status) {

    } allDataReceivedBlock:^(NSUInteger sendBirdDataType, int count) {

    } messageDeliveryBlock:^(BOOL send, NSString *message, NSString *data, NSString *messageId) {

    }];

    if (tf) {
        [[SendBird queryMessageListInChannel:[currentChannel url]] prevWithMessageTs:LLONG_MAX andLimit:50 resultBlock:^(NSMutableArray *queryResult) {
            for (SendBirdMessage *message in queryResult) {
                if ([message isPast]) {
                    [messages insertObject:message atIndex:0];
                }
                else {
                    [messages addObject:message];
                }

                if (lastMessageTimestamp < [message getMessageTimestamp]) {
                    lastMessageTimestamp = [message getMessageTimestamp];
                }

                if (firstMessageTimestamp > [message getMessageTimestamp]) {
                    firstMessageTimestamp = [message getMessageTimestamp];
                }

            }
            [self scrollToBottomWithReloading:YES animated:NO];
            scrollLocked = NO;
            [SendBird connectWithMessageTs:LLONG_MAX];
        } endBlock:^(NSError *error) {

        }];
    }
    else {
        [SendBird connect];
    }
}

There are many blocks in this code. See this link for detail.

Insert the following code to start the open chat at the bottom of viewDidLoad method.

- (void)viewDidLoad {
    //...

    [self startChattingWithPreviousMessage:YES];
}

To send a message, modify sendMessage method. This method is invoked by clicking the “Send” button or pressing the return key. SendBird sendMessage: method sends message in real-time.

- (void) sendMessage
{
    NSString *message = [self.messageTextField text];
    if ([message length] > 0) {
        [self.messageTextField setText:@""];
        [SendBird sendMessage:message];
    }
    scrollLocked = NO;
}

If you click the File button, clickSendFileButton: method will be invoked to open UIImagePickerController. Since UIImagePickerController is used to select an image to send, modify the following method. SendBird uploadFile:type:hasSizeOfFile:withCustomField:uploadBlock: which uploads the imageFileData to SendBird server. This method returns SendBirdFileInfo object which can be sent through SendBird sendFile:.

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    __block NSString *mediaType = [info objectForKey: UIImagePickerControllerMediaType];
    __block UIImage *originalImage, *editedImage, *imageToUse;
    __block NSURL *imagePath;
    __block NSString *imageName;

    [picker dismissViewControllerAnimated:YES completion:^{
        if (CFStringCompare ((CFStringRef) mediaType, kUTTypeImage, 0) == kCFCompareEqualTo) {
            editedImage = (UIImage *) [info objectForKey:
                                       UIImagePickerControllerEditedImage];
            originalImage = (UIImage *) [info objectForKey:
                                         UIImagePickerControllerOriginalImage];

            if (originalImage) {
                imageToUse = originalImage;
            } else {
                imageToUse = editedImage;
            }

            NSData *imageFileData = UIImagePNGRepresentation(imageToUse);
            imagePath = [info objectForKey:@"UIImagePickerControllerReferenceURL"];
            imageName = [imagePath lastPathComponent];

            [SendBird uploadFile:imageFileData type:@"image/jpg" hasSizeOfFile:[imageFileData length] withCustomField:@"" uploadBlock:^(SendBirdFileInfo *fileInfo, NSError *error) {
                openImagePicker = NO;
                [SendBird sendFile:fileInfo];
            }];
        }
    }];
}

Load Previous Messages

To view previous messages in the channel, implement loadPreviousMessages method. This method will be invoked when `openChatChattingTableView draws the first cell.

- (void) loadPreviousMessages {
    if (isLoadingMessage) {
        return;
    }
    isLoadingMessage = YES;

    [self.prevMessageLoadingIndicator setHidden:NO];
    [self.prevMessageLoadingIndicator startAnimating];
    [[SendBird queryMessageListInChannel:[currentChannel url]] prevWithMessageTs:firstMessageTimestamp andLimit:50 resultBlock:^(NSMutableArray *queryResult) {
        NSMutableArray *newMessages = [[NSMutableArray alloc] init];
        for (SendBirdMessage *message in queryResult) {
            if ([message isPast]) {
                [newMessages insertObject:message atIndex:0];
            }
            else {
                [newMessages addObject:message];
            }

            if (lastMessageTimestamp < [message getMessageTimestamp]) {
                lastMessageTimestamp = [message getMessageTimestamp];
            }

            if (firstMessageTimestamp > [message getMessageTimestamp]) {
                firstMessageTimestamp = [message getMessageTimestamp];
            }
        }
        NSUInteger newMsgCount = [newMessages count];

        if (newMsgCount > 0) {
            [messages insertObjects:newMessages atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newMsgCount)]];
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self.openChatChattingTableView reloadData];
                if ([newMessages count] > 0) {
                    [self.openChatChattingTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([newMessages count] - 1) inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];
                }
                isLoadingMessage = NO;
                [self.prevMessageLoadingIndicator setHidden:YES];
                [self.prevMessageLoadingIndicator stopAnimating];
            });
        }
        else {
            isLoadingMessage = NO;
            [self.prevMessageLoadingIndicator setHidden:YES];
            [self.prevMessageLoadingIndicator stopAnimating];
        }
    } endBlock:^(NSError *error) {
        isLoadingMessage = NO;
        [self.prevMessageLoadingIndicator setHidden:YES];
        [self.prevMessageLoadingIndicator stopAnimating];
    }];
}

Run Sample Project

Build the project, join a channel and send a message. You need two devices or one device and an iOS simulator for testing. If you don’t have an extra device, you can use OPERATIONS on SendBird Dashboard to chat using an iOS simulator.

Run the project