Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Chat with iOS and Objective-C


(warning)

Warning

As the Programmable Chat API is set to sunset in 2022(link takes you to an external page), we will no longer maintain these chat tutorials.

Please see our Conversations API QuickStart to start building robust virtual spaces for conversation.

(error)

Danger

Programmable Chat has been deprecated and is no longer supported. Instead, we'll be focusing on the next generation of chat: Twilio Conversations. Find out more about the EOL process here(link takes you to an external page).

If you're starting a new project, please visit the Conversations Docs to begin. If you've already built on Programmable Chat, please visit our Migration Guide to learn about how to switch.

Ready to implement a chat application using Twilio Programmable Chat Client? Here is how it works at a high level:

  1. Twilio Programmable Chat(link takes you to an external page) is the core product we'll be using to handle all the chat functionality.
  2. We use a server side app to generate user access tokens which contains all your Twilio account information. The Chat Client uses this token to connect with the API

Properati built a web and mobile messaging app to help real estate buyers and sellers connect in real time. Learn more here.(link takes you to an external page)

For your convenience, we consolidated the source code for this tutorial in a single GitHub repository(link takes you to an external page). Feel free to clone it and tweak it as required.


Initialize the Chat Client

initialize-the-chat-client page anchor

The only thing you need to create a client is an access token. This token holds information about your Twilio account and Chat API keys. We have created a web version of Twilio chat in different languages. You can use any of these to generate the token:

We use AFNetworking(link takes you to an external page) to make a request to our server and get the access token.

Fetch Access Token

fetch-access-token page anchor

twiliochat/MessagingManager.m


_210
#import "MessagingManager.h"
_210
#import "ChannelManager.h"
_210
#import "SessionManager.h"
_210
#import "TokenRequestHandler.h"
_210
_210
@interface MessagingManager ()
_210
@property (strong, nonatomic) TwilioChatClient *client;
_210
@property (nonatomic, getter=isConnected) BOOL connected;
_210
@end
_210
_210
static NSString * const TWCLoginViewControllerName = @"LoginViewController";
_210
static NSString * const TWCMainViewControllerName = @"RevealViewController";
_210
_210
static NSString * const TWCTokenKey = @"token";
_210
_210
@implementation MessagingManager
_210
+ (instancetype)sharedManager {
_210
static MessagingManager *sharedMyManager = nil;
_210
static dispatch_once_t onceToken;
_210
dispatch_once(&onceToken, ^{
_210
sharedMyManager = [[self alloc] init];
_210
});
_210
return sharedMyManager;
_210
}
_210
_210
- (instancetype)init {
_210
self.delegate = [ChannelManager sharedManager];
_210
return self;
_210
}
_210
_210
# pragma mark Present view controllers
_210
_210
- (void)presentRootViewController {
_210
if (!self.isLoggedIn) {
_210
[self presentViewControllerByName:TWCLoginViewControllerName];
_210
return;
_210
}
_210
if (!self.isConnected) {
_210
[self connectClientWithCompletion:^(BOOL success, NSError *error) {
_210
if (success) {
_210
NSLog(@"Successfully connected chat client");
_210
}
_210
}];
_210
}
_210
_210
}
_210
_210
- (void)presentViewControllerByName:(NSString *)viewController {
_210
[self presentViewController:[[self storyboardWithName:@"Main"] instantiateViewControllerWithIdentifier:viewController]];
_210
}
_210
_210
- (void)presentLaunchScreen {
_210
[self presentViewController:[[self storyboardWithName:@"LaunchScreen"] instantiateInitialViewController]];
_210
}
_210
_210
- (void)presentViewController:(UIViewController *)viewController {
_210
UIWindow *window = [[UIApplication sharedApplication].delegate window];
_210
window.rootViewController = viewController;
_210
}
_210
_210
- (UIStoryboard *)storyboardWithName:(NSString *)name {
_210
return [UIStoryboard storyboardWithName: name bundle: [NSBundle mainBundle]];
_210
}
_210
_210
# pragma mark User and session management
_210
_210
- (BOOL)isLoggedIn {
_210
return [SessionManager isLoggedIn];
_210
}
_210
_210
- (void)loginWithUsername:(NSString *)username
_210
completion:(StatusWithErrorHandler)completion {
_210
[SessionManager loginWithUsername:username];
_210
[self connectClientWithCompletion:^(BOOL success, NSError *error) {
_210
if (success) {
_210
[self presentViewControllerByName:TWCMainViewControllerName];
_210
}
_210
completion(success, error);
_210
}];
_210
}
_210
_210
- (void)logout {
_210
[SessionManager logout];
_210
self.connected = NO;
_210
_210
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_210
[self.client shutdown];
_210
self.client = nil;
_210
});
_210
}
_210
_210
# pragma mark Twilio client
_210
_210
- (void)connectClientWithCompletion:(StatusWithErrorHandler)completion {
_210
if (self.client) {
_210
[self logout];
_210
}
_210
_210
[self requestTokenWithCompletion:^(BOOL succeeded, NSString *token) {
_210
if (succeeded) {
_210
[self initializeClientWithToken:token];
_210
if (completion) completion(succeeded, nil);
_210
}
_210
else {
_210
NSError *error = [self errorWithDescription:@"Could not get access token" code:301];
_210
if (completion) completion(succeeded, error);
_210
}
_210
}];
_210
}
_210
_210
- (void)initializeClientWithToken:(NSString *)token {
_210
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
_210
_210
[TwilioChatClient chatClientWithToken:token
_210
properties:nil
_210
delegate:self
_210
completion:^(TCHResult * _Nonnull result, TwilioChatClient * _Nullable chatClient) {
_210
if (result.isSuccessful) {
_210
self.client = chatClient;
_210
self.connected = YES;
_210
_210
}
_210
}];
_210
}
_210
_210
- (void)requestTokenWithCompletion:(StatusWithTokenHandler)completion {
_210
NSString *uuid = [[UIDevice currentDevice] identifierForVendor].UUIDString;
_210
NSDictionary *parameters = @{@"device": uuid, @"identity": [SessionManager getUsername]};
_210
_210
[TokenRequestHandler fetchTokenWithParams:parameters completion:^(NSDictionary *results, NSError *error) {
_210
NSString *token = [results objectForKey:TWCTokenKey];
_210
BOOL errorCondition = error || !token;
_210
_210
if (completion) completion(!errorCondition, token);
_210
}];
_210
}
_210
_210
- (void)loadGeneralChatRoomWithCompletion:(StatusWithErrorHandler)completion {
_210
[[ChannelManager sharedManager] joinGeneralChatRoomWithCompletion:^(BOOL succeeded) {
_210
if (succeeded)
_210
{
_210
if (completion) completion(succeeded, nil);
_210
}
_210
else {
_210
NSError *error = [self errorWithDescription:@"Could not join General channel" code:300];
_210
if (completion) completion(succeeded, error);
_210
}
_210
}];
_210
}
_210
_210
- (NSError *)errorWithDescription:(NSString *)description code:(NSInteger)code {
_210
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description};
_210
NSError *error = [NSError errorWithDomain:@"app" code:code userInfo:userInfo];
_210
return error;
_210
}
_210
_210
#pragma mark Internal helpers
_210
_210
- (NSString *)userIdentity {
_210
return [SessionManager getUsername];
_210
}
_210
_210
- (void)refreshChatToken:(TwilioChatClient*)client {
_210
[self requestTokenWithCompletion:^(BOOL succeeded, NSString *token) {
_210
if (succeeded) {
_210
[client updateToken:token completion:^(TCHResult * _Nonnull result) {
_210
if (result.isSuccessful) {
_210
_210
}
_210
}];
_210
}
_210
else {
_210
NSLog(@"Error while trying to get new access token");
_210
}
_210
}];
_210
}
_210
_210
#pragma mark TwilioChatClientDelegate
_210
_210
- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel {
_210
[self.delegate chatClient:client channelAdded:channel];
_210
}
_210
_210
- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
_210
[self.delegate chatClient:client channelDeleted:channel];
_210
}
_210
_210
- (void)chatClient:(TwilioChatClient *)client synchronizationStatusUpdated:(TCHClientSynchronizationStatus)status {
_210
if (status == TCHClientSynchronizationStatusCompleted) {
_210
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
_210
[ChannelManager sharedManager].channelsList = client.channelsList;
_210
[[ChannelManager sharedManager] populateChannels];
_210
[self loadGeneralChatRoomWithCompletion:^(BOOL success, NSError *error) {
_210
if (success) {
_210
[self presentViewControllerByName:TWCMainViewControllerName];
_210
}
_210
}];
_210
}
_210
[self.delegate chatClient:client synchronizationStatusUpdated:status];
_210
}
_210
_210
- (void)chatClientTokenWillExpire:(TwilioChatClient *)client {
_210
[self refreshChatToken:client];
_210
}
_210
_210
- (void)chatClientTokenExpired:(TwilioChatClient *)client {
_210
[self refreshChatToken:client];
_210
}
_210
_210
@end

Now it's time to synchronize your Twilio client.


Synchronize the Chat Client

synchronize-the-chat-client page anchor

The synchronizationStatusUpdated delegate(link takes you to an external page) method will allow us to know when the client has loaded all the required information. You can change the default initialization values for the client using a TwilioChatClientProperties(link takes you to an external page) instance as the options parameter in the previews step.

We need the client to be synchronized before trying to get the channel list. Otherwise, calling client.channelsList()(link takes you to an external page) will return nil.

twiliochat/MessagingManager.m


_210
#import "MessagingManager.h"
_210
#import "ChannelManager.h"
_210
#import "SessionManager.h"
_210
#import "TokenRequestHandler.h"
_210
_210
@interface MessagingManager ()
_210
@property (strong, nonatomic) TwilioChatClient *client;
_210
@property (nonatomic, getter=isConnected) BOOL connected;
_210
@end
_210
_210
static NSString * const TWCLoginViewControllerName = @"LoginViewController";
_210
static NSString * const TWCMainViewControllerName = @"RevealViewController";
_210
_210
static NSString * const TWCTokenKey = @"token";
_210
_210
@implementation MessagingManager
_210
+ (instancetype)sharedManager {
_210
static MessagingManager *sharedMyManager = nil;
_210
static dispatch_once_t onceToken;
_210
dispatch_once(&onceToken, ^{
_210
sharedMyManager = [[self alloc] init];
_210
});
_210
return sharedMyManager;
_210
}
_210
_210
- (instancetype)init {
_210
self.delegate = [ChannelManager sharedManager];
_210
return self;
_210
}
_210
_210
# pragma mark Present view controllers
_210
_210
- (void)presentRootViewController {
_210
if (!self.isLoggedIn) {
_210
[self presentViewControllerByName:TWCLoginViewControllerName];
_210
return;
_210
}
_210
if (!self.isConnected) {
_210
[self connectClientWithCompletion:^(BOOL success, NSError *error) {
_210
if (success) {
_210
NSLog(@"Successfully connected chat client");
_210
}
_210
}];
_210
}
_210
_210
}
_210
_210
- (void)presentViewControllerByName:(NSString *)viewController {
_210
[self presentViewController:[[self storyboardWithName:@"Main"] instantiateViewControllerWithIdentifier:viewController]];
_210
}
_210
_210
- (void)presentLaunchScreen {
_210
[self presentViewController:[[self storyboardWithName:@"LaunchScreen"] instantiateInitialViewController]];
_210
}
_210
_210
- (void)presentViewController:(UIViewController *)viewController {
_210
UIWindow *window = [[UIApplication sharedApplication].delegate window];
_210
window.rootViewController = viewController;
_210
}
_210
_210
- (UIStoryboard *)storyboardWithName:(NSString *)name {
_210
return [UIStoryboard storyboardWithName: name bundle: [NSBundle mainBundle]];
_210
}
_210
_210
# pragma mark User and session management
_210
_210
- (BOOL)isLoggedIn {
_210
return [SessionManager isLoggedIn];
_210
}
_210
_210
- (void)loginWithUsername:(NSString *)username
_210
completion:(StatusWithErrorHandler)completion {
_210
[SessionManager loginWithUsername:username];
_210
[self connectClientWithCompletion:^(BOOL success, NSError *error) {
_210
if (success) {
_210
[self presentViewControllerByName:TWCMainViewControllerName];
_210
}
_210
completion(success, error);
_210
}];
_210
}
_210
_210
- (void)logout {
_210
[SessionManager logout];
_210
self.connected = NO;
_210
_210
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_210
[self.client shutdown];
_210
self.client = nil;
_210
});
_210
}
_210
_210
# pragma mark Twilio client
_210
_210
- (void)connectClientWithCompletion:(StatusWithErrorHandler)completion {
_210
if (self.client) {
_210
[self logout];
_210
}
_210
_210
[self requestTokenWithCompletion:^(BOOL succeeded, NSString *token) {
_210
if (succeeded) {
_210
[self initializeClientWithToken:token];
_210
if (completion) completion(succeeded, nil);
_210
}
_210
else {
_210
NSError *error = [self errorWithDescription:@"Could not get access token" code:301];
_210
if (completion) completion(succeeded, error);
_210
}
_210
}];
_210
}
_210
_210
- (void)initializeClientWithToken:(NSString *)token {
_210
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
_210
_210
[TwilioChatClient chatClientWithToken:token
_210
properties:nil
_210
delegate:self
_210
completion:^(TCHResult * _Nonnull result, TwilioChatClient * _Nullable chatClient) {
_210
if (result.isSuccessful) {
_210
self.client = chatClient;
_210
self.connected = YES;
_210
_210
}
_210
}];
_210
}
_210
_210
- (void)requestTokenWithCompletion:(StatusWithTokenHandler)completion {
_210
NSString *uuid = [[UIDevice currentDevice] identifierForVendor].UUIDString;
_210
NSDictionary *parameters = @{@"device": uuid, @"identity": [SessionManager getUsername]};
_210
_210
[TokenRequestHandler fetchTokenWithParams:parameters completion:^(NSDictionary *results, NSError *error) {
_210
NSString *token = [results objectForKey:TWCTokenKey];
_210
BOOL errorCondition = error || !token;
_210
_210
if (completion) completion(!errorCondition, token);
_210
}];
_210
}
_210
_210
- (void)loadGeneralChatRoomWithCompletion:(StatusWithErrorHandler)completion {
_210
[[ChannelManager sharedManager] joinGeneralChatRoomWithCompletion:^(BOOL succeeded) {
_210
if (succeeded)
_210
{
_210
if (completion) completion(succeeded, nil);
_210
}
_210
else {
_210
NSError *error = [self errorWithDescription:@"Could not join General channel" code:300];
_210
if (completion) completion(succeeded, error);
_210
}
_210
}];
_210
}
_210
_210
- (NSError *)errorWithDescription:(NSString *)description code:(NSInteger)code {
_210
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description};
_210
NSError *error = [NSError errorWithDomain:@"app" code:code userInfo:userInfo];
_210
return error;
_210
}
_210
_210
#pragma mark Internal helpers
_210
_210
- (NSString *)userIdentity {
_210
return [SessionManager getUsername];
_210
}
_210
_210
- (void)refreshChatToken:(TwilioChatClient*)client {
_210
[self requestTokenWithCompletion:^(BOOL succeeded, NSString *token) {
_210
if (succeeded) {
_210
[client updateToken:token completion:^(TCHResult * _Nonnull result) {
_210
if (result.isSuccessful) {
_210
_210
}
_210
}];
_210
}
_210
else {
_210
NSLog(@"Error while trying to get new access token");
_210
}
_210
}];
_210
}
_210
_210
#pragma mark TwilioChatClientDelegate
_210
_210
- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel {
_210
[self.delegate chatClient:client channelAdded:channel];
_210
}
_210
_210
- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
_210
[self.delegate chatClient:client channelDeleted:channel];
_210
}
_210
_210
- (void)chatClient:(TwilioChatClient *)client synchronizationStatusUpdated:(TCHClientSynchronizationStatus)status {
_210
if (status == TCHClientSynchronizationStatusCompleted) {
_210
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
_210
[ChannelManager sharedManager].channelsList = client.channelsList;
_210
[[ChannelManager sharedManager] populateChannels];
_210
[self loadGeneralChatRoomWithCompletion:^(BOOL success, NSError *error) {
_210
if (success) {
_210
[self presentViewControllerByName:TWCMainViewControllerName];
_210
}
_210
}];
_210
}
_210
[self.delegate chatClient:client synchronizationStatusUpdated:status];
_210
}
_210
_210
- (void)chatClientTokenWillExpire:(TwilioChatClient *)client {
_210
[self refreshChatToken:client];
_210
}
_210
_210
- (void)chatClientTokenExpired:(TwilioChatClient *)client {
_210
[self refreshChatToken:client];
_210
}
_210
_210
@end

We've initialized the Programmable Chat Client, now let's get a list of channels.


Our ChannelManager class takes care of everything related to channels. In the previous step, we waited for the client to synchronize channel information, and assigned an instance of TCHChannelList(link takes you to an external page) to our ChannelManager. Now we must get an actual array of channels using the userChannelsWithCompletion and publicChannelsWithCompletion methods.

twiliochat/ChannelManager.m


_190
#import "ChannelManager.h"
_190
#import "MessagingManager.h"
_190
_190
#define _ Underscore
_190
_190
@interface ChannelManager ()
_190
@property (strong, nonatomic) TCHChannel *generalChannel;
_190
@end
_190
_190
static NSString * const TWCDefaultChannelUniqueName = @"general";
_190
static NSString * const TWCDefaultChannelName = @"General Channel";
_190
_190
static NSString * const TWCFriendlyNameKey = @"friendlyName";
_190
_190
@implementation ChannelManager
_190
_190
+ (instancetype)sharedManager {
_190
static ChannelManager *sharedMyManager = nil;
_190
static dispatch_once_t onceToken;
_190
dispatch_once(&onceToken, ^{
_190
sharedMyManager = [[self alloc] init];
_190
});
_190
return sharedMyManager;
_190
}
_190
_190
- (instancetype)init {
_190
self.channels = [[NSMutableOrderedSet alloc] init];
_190
return self;
_190
}
_190
_190
#pragma mark General channel
_190
_190
- (void)joinGeneralChatRoomWithCompletion:(SucceedHandler)completion {
_190
[self.channelsList channelWithSidOrUniqueName:TWCDefaultChannelUniqueName completion:^(TCHResult *result, TCHChannel *channel) {
_190
if ([result isSuccessful]) {
_190
self.generalChannel = channel;
_190
}
_190
_190
if (self.generalChannel) {
_190
[self joinGeneralChatRoomWithUniqueName:nil completion:completion];
_190
}
_190
else {
_190
[self createGeneralChatRoomWithCompletion:^(BOOL succeeded) {
_190
if (succeeded) {
_190
[self joinGeneralChatRoomWithUniqueName:TWCDefaultChannelUniqueName completion:completion];
_190
return;
_190
}
_190
if (completion) completion(NO);
_190
}];
_190
};
_190
}];
_190
}
_190
_190
- (void)joinGeneralChatRoomWithUniqueName:(NSString *)uniqueName completion:(SucceedHandler)completion {
_190
[self.generalChannel joinWithCompletion:^(TCHResult *result) {
_190
if ([result isSuccessful]) {
_190
if (uniqueName) {
_190
[self setGeneralChatRoomUniqueNameWithCompletion:completion];
_190
return;
_190
}
_190
}
_190
if (completion) completion([result isSuccessful]);
_190
}];
_190
}
_190
_190
- (void)createGeneralChatRoomWithCompletion:(SucceedHandler)completion {
_190
NSDictionary *options = [
_190
NSDictionary
_190
dictionaryWithObjectsAndKeys:TWCDefaultChannelName,
_190
TCHChannelOptionFriendlyName,
_190
TCHChannelTypePublic,
_190
TCHChannelOptionType,
_190
nil
_190
];
_190
_190
[self.channelsList createChannelWithOptions:options
_190
completion:^(TCHResult *result, TCHChannel *channel) {
_190
if ([result isSuccessful]) {
_190
self.generalChannel = channel;
_190
}
_190
if (completion) completion([result isSuccessful]);
_190
}];
_190
}
_190
_190
- (void)setGeneralChatRoomUniqueNameWithCompletion:(SucceedHandler)completion {
_190
[self.generalChannel setUniqueName:TWCDefaultChannelUniqueName
_190
completion:^(TCHResult *result) {
_190
if (completion) completion([result isSuccessful]);
_190
}];
_190
}
_190
_190
#pragma mark Populate channels
_190
_190
- (void)populateChannels {
_190
self.channels = [[NSMutableOrderedSet alloc] init];
_190
[self.channelsList userChannelDescriptorsWithCompletion:^(TCHResult * _Nonnull result, TCHChannelDescriptorPaginator * _Nullable channelPaginator) {
_190
[self.channels addObjectsFromArray:[channelPaginator items]];
_190
[self sortAndDedupeChannels];
_190
if (self.delegate) {
_190
[self.delegate reloadChannelList];
_190
}
_190
}];
_190
_190
[self.channelsList publicChannelDescriptorsWithCompletion:^(TCHResult *result,
_190
TCHChannelDescriptorPaginator *channelDescPaginator) {
_190
[self.channels addObjectsFromArray: [channelDescPaginator items]];
_190
[self sortAndDedupeChannels];
_190
if (self.delegate) {
_190
[self.delegate reloadChannelList];
_190
}
_190
}];
_190
}
_190
_190
- (void)sortAndDedupeChannels {
_190
NSMutableDictionary *channelsDict = [[NSMutableDictionary alloc] init];
_190
_190
for(TCHChannel *channel in self.channels) {
_190
if (![channelsDict objectForKey: channel.sid] ||
_190
![[channelsDict objectForKey: channel.sid] isKindOfClass: [NSNull class]]) {
_190
[channelsDict setObject:channel forKey:channel.sid];
_190
}
_190
}
_190
_190
NSMutableOrderedSet *dedupedChannels = [NSMutableOrderedSet
_190
orderedSetWithArray:[channelsDict allValues]];
_190
_190
SEL sortSelector = @selector(localizedCaseInsensitiveCompare:);
_190
_190
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:TWCFriendlyNameKey
_190
ascending:YES
_190
selector:sortSelector];
_190
_190
[dedupedChannels sortUsingDescriptors:@[descriptor]];
_190
_190
self.channels = dedupedChannels;
_190
}
_190
_190
# pragma mark Create channel
_190
_190
- (void)createChannelWithName:(NSString *)name completion:(ChannelHandler)completion {
_190
if ([name isEqualToString:TWCDefaultChannelName]) {
_190
if (completion) completion(NO, nil);
_190
return;
_190
}
_190
_190
NSDictionary *options = [
_190
NSDictionary
_190
dictionaryWithObjectsAndKeys:name,
_190
TCHChannelOptionFriendlyName,
_190
TCHChannelTypePublic,
_190
TCHChannelOptionType,
_190
nil
_190
];
_190
[self.channelsList
_190
createChannelWithOptions:options
_190
completion:^(TCHResult *result, TCHChannel *channel) {
_190
[self.channels addObject:channel];
_190
[self sortAndDedupeChannels];
_190
if (completion) completion([result isSuccessful], channel);
_190
}];
_190
}
_190
_190
# pragma mark TwilioChatClientDelegate
_190
_190
- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel{
_190
dispatch_async(dispatch_get_main_queue(), ^{
_190
[self.channels addObject:channel];
_190
[self sortAndDedupeChannels];
_190
[self.delegate chatClient:client channelAdded:channel];
_190
});
_190
}
_190
_190
- (void)chatClient:(TwilioChatClient *)client channel:(nonnull TCHChannel *)channel updated:(TCHChannelUpdate)updated {
_190
dispatch_async(dispatch_get_main_queue(), ^{
_190
[self.delegate chatClient:client channel:channel updated:updated];
_190
});
_190
}
_190
_190
- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
_190
dispatch_async(dispatch_get_main_queue(), ^{
_190
[[ChannelManager sharedManager].channels removeObject:channel];
_190
[self.delegate chatClient:client channelDeleted:channel];
_190
});
_190
}
_190
_190
- (void)chatClient:(TwilioChatClient *)client synchronizationStatusUpdated:(TCHClientSynchronizationStatus)status {
_190
_190
}
_190
_190
@end

Let's see how we can listen to events from the chat client so we can update our app's state.


The Chat Client will trigger events such as channelAdded or channelDeleted on our application. Given the creation or deletion of a channel, we'll reload the channel list in the reveal controller. If a channel is deleted and we were currently joined to that channel, the application will automatically join the general channel.

ChannelManager is a TwilioChatClientDelegate(link takes you to an external page). In this class we implement the delegate methods, but we also allow MenuViewController class to be a delegate of ChannelManager, so it can listen to client events too.

Listen for Client Events

listen-for-client-events page anchor

twiliochat/ChannelManager.m


_190
#import "ChannelManager.h"
_190
#import "MessagingManager.h"
_190
_190
#define _ Underscore
_190
_190
@interface ChannelManager ()
_190
@property (strong, nonatomic) TCHChannel *generalChannel;
_190
@end
_190
_190
static NSString * const TWCDefaultChannelUniqueName = @"general";
_190
static NSString * const TWCDefaultChannelName = @"General Channel";
_190
_190
static NSString * const TWCFriendlyNameKey = @"friendlyName";
_190
_190
@implementation ChannelManager
_190
_190
+ (instancetype)sharedManager {
_190
static ChannelManager *sharedMyManager = nil;
_190
static dispatch_once_t onceToken;
_190
dispatch_once(&onceToken, ^{
_190
sharedMyManager = [[self alloc] init];
_190
});
_190
return sharedMyManager;
_190
}
_190
_190
- (instancetype)init {
_190
self.channels = [[NSMutableOrderedSet alloc] init];
_190
return self;
_190
}
_190
_190
#pragma mark General channel
_190
_190
- (void)joinGeneralChatRoomWithCompletion:(SucceedHandler)completion {
_190
[self.channelsList channelWithSidOrUniqueName:TWCDefaultChannelUniqueName completion:^(TCHResult *result, TCHChannel *channel) {
_190
if ([result isSuccessful]) {
_190
self.generalChannel = channel;
_190
}
_190
_190
if (self.generalChannel) {
_190
[self joinGeneralChatRoomWithUniqueName:nil completion:completion];
_190
}
_190
else {
_190
[self createGeneralChatRoomWithCompletion:^(BOOL succeeded) {
_190
if (succeeded) {
_190
[self joinGeneralChatRoomWithUniqueName:TWCDefaultChannelUniqueName completion:completion];
_190
return;
_190
}
_190
if (completion) completion(NO);
_190
}];
_190
};
_190
}];
_190
}
_190
_190
- (void)joinGeneralChatRoomWithUniqueName:(NSString *)uniqueName completion:(SucceedHandler)completion {
_190
[self.generalChannel joinWithCompletion:^(TCHResult *result) {
_190
if ([result isSuccessful]) {
_190
if (uniqueName) {
_190
[self setGeneralChatRoomUniqueNameWithCompletion:completion];
_190
return;
_190
}
_190
}
_190
if (completion) completion([result isSuccessful]);
_190
}];
_190
}
_190
_190
- (void)createGeneralChatRoomWithCompletion:(SucceedHandler)completion {
_190
NSDictionary *options = [
_190
NSDictionary
_190
dictionaryWithObjectsAndKeys:TWCDefaultChannelName,
_190
TCHChannelOptionFriendlyName,
_190
TCHChannelTypePublic,
_190
TCHChannelOptionType,
_190
nil
_190
];
_190
_190
[self.channelsList createChannelWithOptions:options
_190
completion:^(TCHResult *result, TCHChannel *channel) {
_190
if ([result isSuccessful]) {
_190
self.generalChannel = channel;
_190
}
_190
if (completion) completion([result isSuccessful]);
_190
}];
_190
}
_190
_190
- (void)setGeneralChatRoomUniqueNameWithCompletion:(SucceedHandler)completion {
_190
[self.generalChannel setUniqueName:TWCDefaultChannelUniqueName
_190
completion:^(TCHResult *result) {
_190
if (completion) completion([result isSuccessful]);
_190
}];
_190
}
_190
_190
#pragma mark Populate channels
_190
_190
- (void)populateChannels {
_190
self.channels = [[NSMutableOrderedSet alloc] init];
_190
[self.channelsList userChannelDescriptorsWithCompletion:^(TCHResult * _Nonnull result, TCHChannelDescriptorPaginator * _Nullable channelPaginator) {
_190
[self.channels addObjectsFromArray:[channelPaginator items]];
_190
[self sortAndDedupeChannels];
_190
if (self.delegate) {
_190
[self.delegate reloadChannelList];
_190
}
_190
}];
_190
_190
[self.channelsList publicChannelDescriptorsWithCompletion:^(TCHResult *result,
_190
TCHChannelDescriptorPaginator *channelDescPaginator) {
_190
[self.channels addObjectsFromArray: [channelDescPaginator items]];
_190
[self sortAndDedupeChannels];
_190
if (self.delegate) {
_190
[self.delegate reloadChannelList];
_190
}
_190
}];
_190
}
_190
_190
- (void)sortAndDedupeChannels {
_190
NSMutableDictionary *channelsDict = [[NSMutableDictionary alloc] init];
_190
_190
for(TCHChannel *channel in self.channels) {
_190
if (![channelsDict objectForKey: channel.sid] ||
_190
![[channelsDict objectForKey: channel.sid] isKindOfClass: [NSNull class]]) {
_190
[channelsDict setObject:channel forKey:channel.sid];
_190
}
_190
}
_190
_190
NSMutableOrderedSet *dedupedChannels = [NSMutableOrderedSet
_190
orderedSetWithArray:[channelsDict allValues]];
_190
_190
SEL sortSelector = @selector(localizedCaseInsensitiveCompare:);
_190
_190
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:TWCFriendlyNameKey
_190
ascending:YES
_190
selector:sortSelector];
_190
_190
[dedupedChannels sortUsingDescriptors:@[descriptor]];
_190
_190
self.channels = dedupedChannels;
_190
}
_190
_190
# pragma mark Create channel
_190
_190
- (void)createChannelWithName:(NSString *)name completion:(ChannelHandler)completion {
_190
if ([name isEqualToString:TWCDefaultChannelName]) {
_190
if (completion) completion(NO, nil);
_190
return;
_190
}
_190
_190
NSDictionary *options = [
_190
NSDictionary
_190
dictionaryWithObjectsAndKeys:name,
_190
TCHChannelOptionFriendlyName,
_190
TCHChannelTypePublic,
_190
TCHChannelOptionType,
_190
nil
_190
];
_190
[self.channelsList
_190
createChannelWithOptions:options
_190
completion:^(TCHResult *result, TCHChannel *channel) {
_190
[self.channels addObject:channel];
_190
[self sortAndDedupeChannels];
_190
if (completion) completion([result isSuccessful], channel);
_190
}];
_190
}
_190
_190
# pragma mark TwilioChatClientDelegate
_190
_190
- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel{
_190
dispatch_async(dispatch_get_main_queue(), ^{
_190
[self.channels addObject:channel];
_190
[self sortAndDedupeChannels];
_190
[self.delegate chatClient:client channelAdded:channel];
_190
});
_190
}
_190
_190
- (void)chatClient:(TwilioChatClient *)client channel:(nonnull TCHChannel *)channel updated:(TCHChannelUpdate)updated {
_190
dispatch_async(dispatch_get_main_queue(), ^{
_190
[self.delegate chatClient:client channel:channel updated:updated];
_190
});
_190
}
_190
_190
- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
_190
dispatch_async(dispatch_get_main_queue(), ^{
_190
[[ChannelManager sharedManager].channels removeObject:channel];
_190
[self.delegate chatClient:client channelDeleted:channel];
_190
});
_190
}
_190
_190
- (void)chatClient:(TwilioChatClient *)client synchronizationStatusUpdated:(TCHClientSynchronizationStatus)status {
_190
_190
}
_190
_190
@end

Next, we need a default channel.


Join the General Channel

join-the-general-channel page anchor

This application will try to join a channel called "General Channel" when it starts. If the channel doesn't exist, it'll create one with that name. The scope of this example application will show you how to work only with public channels, but the Chat client allows you to create private channels and handle invitations.

Once you have joined a channel, you can register a class as the TCHChannelDelegate so you can start listening to events such as messageAdded or memberJoined. We'll show you how to do this in the next step.

Join or Create a General Channel

join-or-create-a-general-channel page anchor

twiliochat/ChannelManager.m


_190
#import "ChannelManager.h"
_190
#import "MessagingManager.h"
_190
_190
#define _ Underscore
_190
_190
@interface ChannelManager ()
_190
@property (strong, nonatomic) TCHChannel *generalChannel;
_190
@end
_190
_190
static NSString * const TWCDefaultChannelUniqueName = @"general";
_190
static NSString * const TWCDefaultChannelName = @"General Channel";
_190
_190
static NSString * const TWCFriendlyNameKey = @"friendlyName";
_190
_190
@implementation ChannelManager
_190
_190
+ (instancetype)sharedManager {
_190
static ChannelManager *sharedMyManager = nil;
_190
static dispatch_once_t onceToken;
_190
dispatch_once(&onceToken, ^{
_190
sharedMyManager = [[self alloc] init];
_190
});
_190
return sharedMyManager;
_190
}
_190
_190
- (instancetype)init {
_190
self.channels = [[NSMutableOrderedSet alloc] init];
_190
return self;
_190
}
_190
_190
#pragma mark General channel
_190
_190
- (void)joinGeneralChatRoomWithCompletion:(SucceedHandler)completion {
_190
[self.channelsList channelWithSidOrUniqueName:TWCDefaultChannelUniqueName completion:^(TCHResult *result, TCHChannel *channel) {
_190
if ([result isSuccessful]) {
_190
self.generalChannel = channel;
_190
}
_190
_190
if (self.generalChannel) {
_190
[self joinGeneralChatRoomWithUniqueName:nil completion:completion];
_190
}
_190
else {
_190
[self createGeneralChatRoomWithCompletion:^(BOOL succeeded) {
_190
if (succeeded) {
_190
[self joinGeneralChatRoomWithUniqueName:TWCDefaultChannelUniqueName completion:completion];
_190
return;
_190
}
_190
if (completion) completion(NO);
_190
}];
_190
};
_190
}];
_190
}
_190
_190
- (void)joinGeneralChatRoomWithUniqueName:(NSString *)uniqueName completion:(SucceedHandler)completion {
_190
[self.generalChannel joinWithCompletion:^(TCHResult *result) {
_190
if ([result isSuccessful]) {
_190
if (uniqueName) {
_190
[self setGeneralChatRoomUniqueNameWithCompletion:completion];
_190
return;
_190
}
_190
}
_190
if (completion) completion([result isSuccessful]);
_190
}];
_190
}
_190
_190
- (void)createGeneralChatRoomWithCompletion:(SucceedHandler)completion {
_190
NSDictionary *options = [
_190
NSDictionary
_190
dictionaryWithObjectsAndKeys:TWCDefaultChannelName,
_190
TCHChannelOptionFriendlyName,
_190
TCHChannelTypePublic,
_190
TCHChannelOptionType,
_190
nil
_190
];
_190
_190
[self.channelsList createChannelWithOptions:options
_190
completion:^(TCHResult *result, TCHChannel *channel) {
_190
if ([result isSuccessful]) {
_190
self.generalChannel = channel;
_190
}
_190
if (completion) completion([result isSuccessful]);
_190
}];
_190
}
_190
_190
- (void)setGeneralChatRoomUniqueNameWithCompletion:(SucceedHandler)completion {
_190
[self.generalChannel setUniqueName:TWCDefaultChannelUniqueName
_190
completion:^(TCHResult *result) {
_190
if (completion) completion([result isSuccessful]);
_190
}];
_190
}
_190
_190
#pragma mark Populate channels
_190
_190
- (void)populateChannels {
_190
self.channels = [[NSMutableOrderedSet alloc] init];
_190
[self.channelsList userChannelDescriptorsWithCompletion:^(TCHResult * _Nonnull result, TCHChannelDescriptorPaginator * _Nullable channelPaginator) {
_190
[self.channels addObjectsFromArray:[channelPaginator items]];
_190
[self sortAndDedupeChannels];
_190
if (self.delegate) {
_190
[self.delegate reloadChannelList];
_190
}
_190
}];
_190
_190
[self.channelsList publicChannelDescriptorsWithCompletion:^(TCHResult *result,
_190
TCHChannelDescriptorPaginator *channelDescPaginator) {
_190
[self.channels addObjectsFromArray: [channelDescPaginator items]];
_190
[self sortAndDedupeChannels];
_190
if (self.delegate) {
_190
[self.delegate reloadChannelList];
_190
}
_190
}];
_190
}
_190
_190
- (void)sortAndDedupeChannels {
_190
NSMutableDictionary *channelsDict = [[NSMutableDictionary alloc] init];
_190
_190
for(TCHChannel *channel in self.channels) {
_190
if (![channelsDict objectForKey: channel.sid] ||
_190
![[channelsDict objectForKey: channel.sid] isKindOfClass: [NSNull class]]) {
_190
[channelsDict setObject:channel forKey:channel.sid];
_190
}
_190
}
_190
_190
NSMutableOrderedSet *dedupedChannels = [NSMutableOrderedSet
_190
orderedSetWithArray:[channelsDict allValues]];
_190
_190
SEL sortSelector = @selector(localizedCaseInsensitiveCompare:);
_190
_190
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:TWCFriendlyNameKey
_190
ascending:YES
_190
selector:sortSelector];
_190
_190
[dedupedChannels sortUsingDescriptors:@[descriptor]];
_190
_190
self.channels = dedupedChannels;
_190
}
_190
_190
# pragma mark Create channel
_190
_190
- (void)createChannelWithName:(NSString *)name completion:(ChannelHandler)completion {
_190
if ([name isEqualToString:TWCDefaultChannelName]) {
_190
if (completion) completion(NO, nil);
_190
return;
_190
}
_190
_190
NSDictionary *options = [
_190
NSDictionary
_190
dictionaryWithObjectsAndKeys:name,
_190
TCHChannelOptionFriendlyName,
_190
TCHChannelTypePublic,
_190
TCHChannelOptionType,
_190
nil
_190
];
_190
[self.channelsList
_190
createChannelWithOptions:options
_190
completion:^(TCHResult *result, TCHChannel *channel) {
_190
[self.channels addObject:channel];
_190
[self sortAndDedupeChannels];
_190
if (completion) completion([result isSuccessful], channel);
_190
}];
_190
}
_190
_190
# pragma mark TwilioChatClientDelegate
_190
_190
- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel{
_190
dispatch_async(dispatch_get_main_queue(), ^{
_190
[self.channels addObject:channel];
_190
[self sortAndDedupeChannels];
_190
[self.delegate chatClient:client channelAdded:channel];
_190
});
_190
}
_190
_190
- (void)chatClient:(TwilioChatClient *)client channel:(nonnull TCHChannel *)channel updated:(TCHChannelUpdate)updated {
_190
dispatch_async(dispatch_get_main_queue(), ^{
_190
[self.delegate chatClient:client channel:channel updated:updated];
_190
});
_190
}
_190
_190
- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
_190
dispatch_async(dispatch_get_main_queue(), ^{
_190
[[ChannelManager sharedManager].channels removeObject:channel];
_190
[self.delegate chatClient:client channelDeleted:channel];
_190
});
_190
}
_190
_190
- (void)chatClient:(TwilioChatClient *)client synchronizationStatusUpdated:(TCHClientSynchronizationStatus)status {
_190
_190
}
_190
_190
@end

Now let's listen for some channel events.


Listen to Channel Events

listen-to-channel-events page anchor

We registered MainChatViewController as the TCHChannelDelegate, and here we implemented the following methods that listen to channel events:

  • channelDeleted : When someone deletes a channel.
  • memberJoined : When someone joins the channel.
  • memberLeft : When someone leaves the channel.
  • messageAdded : When someone sends a message to the channel you are connected to.
  • synchronizationStatusChanged : When channel synchronization status changes.

As you may have noticed, each one of these methods includes useful objects as parameters. One example is the actual message that was added to the channel.

twiliochat/MainChatViewController.m


_319
#import <TwilioChatClient/TwilioChatClient.h>
_319
#import "MainChatViewController.h"
_319
#import "ChatTableCell.h"
_319
#import "NSDate+ISO8601Parser.h"
_319
#import "SWRevealViewController.h"
_319
#import "ChannelManager.h"
_319
#import "StatusEntry.h"
_319
#import "DateTodayFormatter.h"
_319
#import "MenuViewController.h"
_319
_319
@interface MainChatViewController ()
_319
@property (weak, nonatomic) IBOutlet UIBarButtonItem *revealButtonItem;
_319
@property (weak, nonatomic) IBOutlet UIBarButtonItem *actionButtonItem;
_319
_319
@property (strong, nonatomic) NSMutableOrderedSet *messages;
_319
_319
@end
_319
_319
static NSString * const TWCChatCellIdentifier = @"ChatTableCell";
_319
static NSString * const TWCChatStatusCellIdentifier = @"ChatStatusTableCell";
_319
_319
static NSString * const TWCOpenGeneralChannelSegue = @"OpenGeneralChat";
_319
static NSInteger const TWCLabelTag = 200;
_319
_319
@implementation MainChatViewController
_319
_319
#pragma mark Initialization
_319
_319
- (void)viewDidLoad {
_319
[super viewDidLoad];
_319
_319
if (self.revealViewController)
_319
{
_319
[self.revealButtonItem setTarget: self.revealViewController];
_319
[self.revealButtonItem setAction: @selector( revealToggle: )];
_319
[self.navigationController.navigationBar addGestureRecognizer: self.revealViewController.panGestureRecognizer];
_319
self.revealViewController.rearViewRevealOverdraw = 0.f;
_319
}
_319
_319
self.bounces = YES;
_319
self.shakeToClearEnabled = YES;
_319
self.keyboardPanningEnabled = YES;
_319
self.shouldScrollToBottomAfterKeyboardShows = NO;
_319
self.inverted = YES;
_319
_319
UINib *cellNib = [UINib nibWithNibName:TWCChatCellIdentifier bundle:nil];
_319
[self.tableView registerNib:cellNib
_319
forCellReuseIdentifier:TWCChatCellIdentifier];
_319
_319
UINib *cellStatusNib = [UINib nibWithNibName:TWCChatStatusCellIdentifier bundle:nil];
_319
[self.tableView registerNib:cellStatusNib
_319
forCellReuseIdentifier:TWCChatStatusCellIdentifier];
_319
_319
self.textInputbar.autoHideRightButton = YES;
_319
self.textInputbar.maxCharCount = 256;
_319
self.textInputbar.counterStyle = SLKCounterStyleSplit;
_319
self.textInputbar.counterPosition = SLKCounterPositionTop;
_319
_319
UIFont *font = [UIFont fontWithName:@"Avenir-Light" size:14];
_319
self.textView.font = font;
_319
_319
[self.rightButton setTitleColor:[UIColor colorWithRed:0.973 green:0.557 blue:0.502 alpha:1]
_319
forState:UIControlStateNormal];
_319
_319
font = [UIFont fontWithName:@"Avenir-Heavy" size:17];
_319
self.navigationController.navigationBar.titleTextAttributes = @{NSFontAttributeName:font};
_319
_319
self.tableView.allowsSelection = NO;
_319
self.tableView.estimatedRowHeight = 70;
_319
self.tableView.rowHeight = UITableViewAutomaticDimension;
_319
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_319
_319
if (!self.channel) {
_319
id generalChannel = [ChannelManager sharedManager].generalChannel;
_319
if (generalChannel) {
_319
self.channel = generalChannel;
_319
} else {
_319
[[ChannelManager sharedManager] joinGeneralChatRoomWithCompletion:^(BOOL succeeded) {
_319
if (succeeded) {
_319
self.channel = [ChannelManager sharedManager].generalChannel;
_319
}
_319
}];
_319
}
_319
}
_319
}
_319
_319
- (void)viewDidLayoutSubviews {
_319
[super viewDidLayoutSubviews];
_319
[self.textInputbar bringSubviewToFront:self.textInputbar.textView];
_319
[self.textInputbar bringSubviewToFront:self.textInputbar.leftButton];
_319
[self.textInputbar bringSubviewToFront:self.textInputbar.rightButton];
_319
}
_319
_319
- (void)viewDidAppear:(BOOL)animated {
_319
[super viewDidAppear:animated];
_319
[self scrollToBottomMessage];
_319
}
_319
_319
- (NSMutableOrderedSet *)messages {
_319
if (!_messages) {
_319
_messages = [[NSMutableOrderedSet alloc] init];
_319
}
_319
return _messages;
_319
}
_319
_319
- (void)setChannel:(TCHChannel *)channel {
_319
if ([channel isKindOfClass:[TCHChannelDescriptor class]]) {
_319
TCHChannelDescriptor *channelDescriptor = (TCHChannelDescriptor*)channel;
_319
[channelDescriptor channelWithCompletion:^(TCHResult *success, TCHChannel *channel) {
_319
if (success) {
_319
[self actuallySetChannel:channel];
_319
}
_319
}];
_319
} else {
_319
[self actuallySetChannel:channel];
_319
}
_319
}
_319
_319
- (void)actuallySetChannel:(TCHChannel *)channel {
_319
_channel = channel;
_319
self.title = self.channel.friendlyName;
_319
self.channel.delegate = self;
_319
_319
if (self.channel == [ChannelManager sharedManager].generalChannel) {
_319
self.navigationItem.rightBarButtonItem = nil;
_319
}
_319
_319
[self setViewOnHold:YES];
_319
_319
if (self.channel.status != TCHChannelStatusJoined) {
_319
[self.channel joinWithCompletion:^(TCHResult* result) {
_319
NSLog(@"%@", @"Channel Joined");
_319
[self setViewOnHold:NO];
_319
}];
_319
}
_319
if (self.channel.synchronizationStatus == TCHChannelSynchronizationStatusAll) {
_319
[self loadMessages];
_319
[self setViewOnHold:NO];
_319
}
_319
}
_319
_319
// Disable user input and show activity indicator
_319
- (void)setViewOnHold:(BOOL)onHold {
_319
self.textInputbarHidden = onHold;
_319
[UIApplication sharedApplication].networkActivityIndicatorVisible = onHold;
_319
}
_319
_319
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
_319
return 1;
_319
}
_319
_319
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
_319
return self.messages.count;
_319
}
_319
_319
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
_319
UITableViewCell *cell = nil;
_319
_319
id message = [self.messages objectAtIndex:indexPath.row];
_319
_319
if ([message isKindOfClass:[TCHMessage class]]) {
_319
cell = [self getChatCellForTableView:tableView forIndexPath:indexPath message:message];
_319
}
_319
else {
_319
cell = [self getStatusCellForTableView:tableView forIndexPath:indexPath message:message];
_319
}
_319
_319
cell.transform = tableView.transform;
_319
return cell;
_319
}
_319
_319
- (ChatTableCell *)getChatCellForTableView:(UITableView *)tableView
_319
forIndexPath:(NSIndexPath *)indexPath
_319
message:(TCHMessage *)message {
_319
UITableViewCell *cell = [self.tableView
_319
dequeueReusableCellWithIdentifier:TWCChatCellIdentifier forIndexPath:indexPath];
_319
_319
ChatTableCell *chatCell = (ChatTableCell *)cell;
_319
chatCell.user = message.author;
_319
chatCell.date = [[[DateTodayFormatter alloc] init]
_319
stringFromDate:[NSDate dateWithISO8601String:message.timestamp]];
_319
_319
chatCell.message = message.body;
_319
_319
return chatCell;
_319
}
_319
_319
- (UITableViewCell *)getStatusCellForTableView:(UITableView *)tableView
_319
forIndexPath:(NSIndexPath *)indexPath
_319
message:(StatusEntry *)message {
_319
UITableViewCell *cell = [self.tableView
_319
dequeueReusableCellWithIdentifier:TWCChatStatusCellIdentifier forIndexPath:indexPath];
_319
_319
UILabel *label = [cell viewWithTag:TWCLabelTag];
_319
label.text = [NSString stringWithFormat:@"User %@ has %@",
_319
message.member.identity, (message.status == TWCMemberStatusJoined) ? @"joined" : @"left"];
_319
_319
return cell;
_319
}
_319
_319
- (void)didPressRightButton:(id)sender {
_319
[self.textView refreshFirstResponder];
_319
[self sendMessage: [self.textView.text copy]];
_319
[super didPressRightButton:sender];
_319
}
_319
_319
#pragma mark Chat Service
_319
- (void)sendMessage: (NSString *)inputMessage {
_319
TCHMessageOptions *messageOptions = [[[TCHMessageOptions alloc] init] withBody:inputMessage];
_319
[self.channel.messages sendMessageWithOptions:messageOptions
_319
completion:nil];
_319
}
_319
_319
_319
_319
- (void)addMessages:(NSArray *)messages {
_319
[self.messages addObjectsFromArray:messages];
_319
[self sortMessages];
_319
dispatch_async(dispatch_get_main_queue(), ^{
_319
[self.tableView reloadData];
_319
if (self.messages.count > 0) {
_319
[self scrollToBottomMessage];
_319
}
_319
});
_319
}
_319
_319
_319
- (void)sortMessages {
_319
[self.messages sortUsingDescriptors:@[[[NSSortDescriptor alloc]
_319
initWithKey:@"timestamp" ascending:NO]]];
_319
}
_319
_319
- (void)scrollToBottomMessage {
_319
if (self.messages.count == 0) {
_319
return;
_319
}
_319
_319
NSIndexPath *bottomMessageIndex = [NSIndexPath indexPathForRow:0
_319
inSection:0];
_319
[self.tableView scrollToRowAtIndexPath:bottomMessageIndex
_319
atScrollPosition:UITableViewScrollPositionBottom animated:NO];
_319
}
_319
_319
- (void)loadMessages {
_319
[self.messages removeAllObjects];
_319
if (self.channel.synchronizationStatus == TCHChannelSynchronizationStatusAll) {
_319
[self.channel.messages
_319
getLastMessagesWithCount:100
_319
completion:^(TCHResult *result, NSArray *messages) {
_319
if ([result isSuccessful]) {
_319
[self addMessages: messages];
_319
}
_319
}];
_319
}
_319
}
_319
_319
- (void)leaveChannel {
_319
[self.channel leaveWithCompletion:^(TCHResult* result) {
_319
if ([result isSuccessful]) {
_319
[(MenuViewController *)self.revealViewController.rearViewController deselectSelectedChannel];
_319
[self.revealViewController.rearViewController
_319
performSegueWithIdentifier:TWCOpenGeneralChannelSegue sender:nil];
_319
}
_319
}];
_319
}
_319
_319
#pragma mark - TMMessageDelegate
_319
_319
- (void)chatClient:(TwilioChatClient *)client
_319
channel:(TCHChannel *)channel
_319
messageAdded:(TCHMessage *)message {
_319
if (![self.messages containsObject:message]) {
_319
[self addMessages:@[message]];
_319
}
_319
}
_319
_319
- (void)chatClient:(TwilioChatClient *)client
_319
channelDeleted:(TCHChannel *)channel {
_319
dispatch_async(dispatch_get_main_queue(), ^{
_319
if (channel == self.channel) {
_319
[self.revealViewController.rearViewController
_319
performSegueWithIdentifier:TWCOpenGeneralChannelSegue sender:nil];
_319
}
_319
});
_319
}
_319
_319
- (void)chatClient:(TwilioChatClient *)client
_319
channel:(TCHChannel *)channel
_319
memberJoined:(TCHMember *)member {
_319
[self addMessages:@[[StatusEntry statusEntryWithMember:member status:TWCMemberStatusJoined]]];
_319
}
_319
_319
- (void)chatClient:(TwilioChatClient *)client
_319
channel:(TCHChannel *)channel
_319
memberLeft:(TCHMember *)member {
_319
[self addMessages:@[[StatusEntry statusEntryWithMember:member status:TWCMemberStatusLeft]]];
_319
}
_319
_319
- (void)chatClient:(TwilioChatClient *)client channel:(TCHChannel *)channel synchronizationStatusChanged:(TCHChannelSynchronizationStatus)status {
_319
if (status == TCHChannelSynchronizationStatusAll) {
_319
[self loadMessages];
_319
dispatch_async(dispatch_get_main_queue(), ^{
_319
[self.tableView reloadData];
_319
[self setViewOnHold:NO];
_319
});
_319
}
_319
}
_319
_319
#pragma mark - Actions
_319
_319
- (IBAction)actionButtonTouched:(UIBarButtonItem *)sender {
_319
[self leaveChannel];
_319
}
_319
_319
- (IBAction)revealButtonTouched:(UIBarButtonItem *)sender {
_319
[self.revealViewController revealToggleAnimated:YES];
_319
}
_319
_319
@end

We've actually got a real chat app going here, but let's make it more interesting with multiple channels.


The application uses SWRevealViewController(link takes you to an external page) to show a sidebar that contains a list of the channels created for that Twilio account.

When you tap on the name of a channel from the sidebar, that channel is set on the MainChatViewController. The setChannel method takes care of joining to the selected channel and loading the messages.

twiliochat/MainChatViewController.m


_319
#import <TwilioChatClient/TwilioChatClient.h>
_319
#import "MainChatViewController.h"
_319
#import "ChatTableCell.h"
_319
#import "NSDate+ISO8601Parser.h"
_319
#import "SWRevealViewController.h"
_319
#import "ChannelManager.h"
_319
#import "StatusEntry.h"
_319
#import "DateTodayFormatter.h"
_319
#import "MenuViewController.h"
_319
_319
@interface MainChatViewController ()
_319
@property (weak, nonatomic) IBOutlet UIBarButtonItem *revealButtonItem;
_319
@property (weak, nonatomic) IBOutlet UIBarButtonItem *actionButtonItem;
_319
_319
@property (strong, nonatomic) NSMutableOrderedSet *messages;
_319
_319
@end
_319
_319
static NSString * const TWCChatCellIdentifier = @"ChatTableCell";
_319
static NSString * const TWCChatStatusCellIdentifier = @"ChatStatusTableCell";
_319
_319
static NSString * const TWCOpenGeneralChannelSegue = @"OpenGeneralChat";
_319
static NSInteger const TWCLabelTag = 200;
_319
_319
@implementation MainChatViewController
_319
_319
#pragma mark Initialization
_319
_319
- (void)viewDidLoad {
_319
[super viewDidLoad];
_319
_319
if (self.revealViewController)
_319
{
_319
[self.revealButtonItem setTarget: self.revealViewController];
_319
[self.revealButtonItem setAction: @selector( revealToggle: )];
_319
[self.navigationController.navigationBar addGestureRecognizer: self.revealViewController.panGestureRecognizer];
_319
self.revealViewController.rearViewRevealOverdraw = 0.f;
_319
}
_319
_319
self.bounces = YES;
_319
self.shakeToClearEnabled = YES;
_319
self.keyboardPanningEnabled = YES;
_319
self.shouldScrollToBottomAfterKeyboardShows = NO;
_319
self.inverted = YES;
_319
_319
UINib *cellNib = [UINib nibWithNibName:TWCChatCellIdentifier bundle:nil];
_319
[self.tableView registerNib:cellNib
_319
forCellReuseIdentifier:TWCChatCellIdentifier];
_319
_319
UINib *cellStatusNib = [UINib nibWithNibName:TWCChatStatusCellIdentifier bundle:nil];
_319
[self.tableView registerNib:cellStatusNib
_319
forCellReuseIdentifier:TWCChatStatusCellIdentifier];
_319
_319
self.textInputbar.autoHideRightButton = YES;
_319
self.textInputbar.maxCharCount = 256;
_319
self.textInputbar.counterStyle = SLKCounterStyleSplit;
_319
self.textInputbar.counterPosition = SLKCounterPositionTop;
_319
_319
UIFont *font = [UIFont fontWithName:@"Avenir-Light" size:14];
_319
self.textView.font = font;
_319
_319
[self.rightButton setTitleColor:[UIColor colorWithRed:0.973 green:0.557 blue:0.502 alpha:1]
_319
forState:UIControlStateNormal];
_319
_319
font = [UIFont fontWithName:@"Avenir-Heavy" size:17];
_319
self.navigationController.navigationBar.titleTextAttributes = @{NSFontAttributeName:font};
_319
_319
self.tableView.allowsSelection = NO;
_319
self.tableView.estimatedRowHeight = 70;
_319
self.tableView.rowHeight = UITableViewAutomaticDimension;
_319
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_319
_319
if (!self.channel) {
_319
id generalChannel = [ChannelManager sharedManager].generalChannel;
_319
if (generalChannel) {
_319
self.channel = generalChannel;
_319
} else {
_319
[[ChannelManager sharedManager] joinGeneralChatRoomWithCompletion:^(BOOL succeeded) {
_319
if (succeeded) {
_319
self.channel = [ChannelManager sharedManager].generalChannel;
_319
}
_319
}];
_319
}
_319
}
_319
}
_319
_319
- (void)viewDidLayoutSubviews {
_319
[super viewDidLayoutSubviews];
_319
[self.textInputbar bringSubviewToFront:self.textInputbar.textView];
_319
[self.textInputbar bringSubviewToFront:self.textInputbar.leftButton];
_319
[self.textInputbar bringSubviewToFront:self.textInputbar.rightButton];
_319
}
_319
_319
- (void)viewDidAppear:(BOOL)animated {
_319
[super viewDidAppear:animated];
_319
[self scrollToBottomMessage];
_319
}
_319
_319
- (NSMutableOrderedSet *)messages {
_319
if (!_messages) {
_319
_messages = [[NSMutableOrderedSet alloc] init];
_319
}
_319
return _messages;
_319
}
_319
_319
- (void)setChannel:(TCHChannel *)channel {
_319
if ([channel isKindOfClass:[TCHChannelDescriptor class]]) {
_319
TCHChannelDescriptor *channelDescriptor = (TCHChannelDescriptor*)channel;
_319
[channelDescriptor channelWithCompletion:^(TCHResult *success, TCHChannel *channel) {
_319
if (success) {
_319
[self actuallySetChannel:channel];
_319
}
_319
}];
_319
} else {
_319
[self actuallySetChannel:channel];
_319
}
_319
}
_319
_319
- (void)actuallySetChannel:(TCHChannel *)channel {
_319
_channel = channel;
_319
self.title = self.channel.friendlyName;
_319
self.channel.delegate = self;
_319
_319
if (self.channel == [ChannelManager sharedManager].generalChannel) {
_319
self.navigationItem.rightBarButtonItem = nil;
_319
}
_319
_319
[self setViewOnHold:YES];
_319
_319
if (self.channel.status != TCHChannelStatusJoined) {
_319
[self.channel joinWithCompletion:^(TCHResult* result) {
_319
NSLog(@"%@", @"Channel Joined");
_319
[self setViewOnHold:NO];
_319
}];
_319
}
_319
if (self.channel.synchronizationStatus == TCHChannelSynchronizationStatusAll) {
_319
[self loadMessages];
_319
[self setViewOnHold:NO];
_319
}
_319
}
_319
_319
// Disable user input and show activity indicator
_319
- (void)setViewOnHold:(BOOL)onHold {
_319
self.textInputbarHidden = onHold;
_319
[UIApplication sharedApplication].networkActivityIndicatorVisible = onHold;
_319
}
_319
_319
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
_319
return 1;
_319
}
_319
_319
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
_319
return self.messages.count;
_319
}
_319
_319
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
_319
UITableViewCell *cell = nil;
_319
_319
id message = [self.messages objectAtIndex:indexPath.row];
_319
_319
if ([message isKindOfClass:[TCHMessage class]]) {
_319
cell = [self getChatCellForTableView:tableView forIndexPath:indexPath message:message];
_319
}
_319
else {
_319
cell = [self getStatusCellForTableView:tableView forIndexPath:indexPath message:message];
_319
}
_319
_319
cell.transform = tableView.transform;
_319
return cell;
_319
}
_319
_319
- (ChatTableCell *)getChatCellForTableView:(UITableView *)tableView
_319
forIndexPath:(NSIndexPath *)indexPath
_319
message:(TCHMessage *)message {
_319
UITableViewCell *cell = [self.tableView
_319
dequeueReusableCellWithIdentifier:TWCChatCellIdentifier forIndexPath:indexPath];
_319
_319
ChatTableCell *chatCell = (ChatTableCell *)cell;
_319
chatCell.user = message.author;
_319
chatCell.date = [[[DateTodayFormatter alloc] init]
_319
stringFromDate:[NSDate dateWithISO8601String:message.timestamp]];
_319
_319
chatCell.message = message.body;
_319
_319
return chatCell;
_319
}
_319
_319
- (UITableViewCell *)getStatusCellForTableView:(UITableView *)tableView
_319
forIndexPath:(NSIndexPath *)indexPath
_319
message:(StatusEntry *)message {
_319
UITableViewCell *cell = [self.tableView
_319
dequeueReusableCellWithIdentifier:TWCChatStatusCellIdentifier forIndexPath:indexPath];
_319
_319
UILabel *label = [cell viewWithTag:TWCLabelTag];
_319
label.text = [NSString stringWithFormat:@"User %@ has %@",
_319
message.member.identity, (message.status == TWCMemberStatusJoined) ? @"joined" : @"left"];
_319
_319
return cell;
_319
}
_319
_319
- (void)didPressRightButton:(id)sender {
_319
[self.textView refreshFirstResponder];
_319
[self sendMessage: [self.textView.text copy]];
_319
[super didPressRightButton:sender];
_319
}
_319
_319
#pragma mark Chat Service
_319
- (void)sendMessage: (NSString *)inputMessage {
_319
TCHMessageOptions *messageOptions = [[[TCHMessageOptions alloc] init] withBody:inputMessage];
_319
[self.channel.messages sendMessageWithOptions:messageOptions
_319
completion:nil];
_319
}
_319
_319
_319
_319
- (void)addMessages:(NSArray *)messages {
_319
[self.messages addObjectsFromArray:messages];
_319
[self sortMessages];
_319
dispatch_async(dispatch_get_main_queue(), ^{
_319
[self.tableView reloadData];
_319
if (self.messages.count > 0) {
_319
[self scrollToBottomMessage];
_319
}
_319
});
_319
}
_319
_319
_319
- (void)sortMessages {
_319
[self.messages sortUsingDescriptors:@[[[NSSortDescriptor alloc]
_319
initWithKey:@"timestamp" ascending:NO]]];
_319
}
_319
_319
- (void)scrollToBottomMessage {
_319
if (self.messages.count == 0) {
_319
return;
_319
}
_319
_319
NSIndexPath *bottomMessageIndex = [NSIndexPath indexPathForRow:0
_319
inSection:0];
_319
[self.tableView scrollToRowAtIndexPath:bottomMessageIndex
_319
atScrollPosition:UITableViewScrollPositionBottom animated:NO];
_319
}
_319
_319
- (void)loadMessages {
_319
[self.messages removeAllObjects];
_319
if (self.channel.synchronizationStatus == TCHChannelSynchronizationStatusAll) {
_319
[self.channel.messages
_319
getLastMessagesWithCount:100
_319
completion:^(TCHResult *result, NSArray *messages) {
_319
if ([result isSuccessful]) {
_319
[self addMessages: messages];
_319
}
_319
}];
_319
}
_319
}
_319
_319
- (void)leaveChannel {
_319
[self.channel leaveWithCompletion:^(TCHResult* result) {
_319
if ([result isSuccessful]) {
_319
[(MenuViewController *)self.revealViewController.rearViewController deselectSelectedChannel];
_319
[self.revealViewController.rearViewController
_319
performSegueWithIdentifier:TWCOpenGeneralChannelSegue sender:nil];
_319
}
_319
}];
_319
}
_319
_319
#pragma mark - TMMessageDelegate
_319
_319
- (void)chatClient:(TwilioChatClient *)client
_319
channel:(TCHChannel *)channel
_319
messageAdded:(TCHMessage *)message {
_319
if (![self.messages containsObject:message]) {
_319
[self addMessages:@[message]];
_319
}
_319
}
_319
_319
- (void)chatClient:(TwilioChatClient *)client
_319
channelDeleted:(TCHChannel *)channel {
_319
dispatch_async(dispatch_get_main_queue(), ^{
_319
if (channel == self.channel) {
_319
[self.revealViewController.rearViewController
_319
performSegueWithIdentifier:TWCOpenGeneralChannelSegue sender:nil];
_319
}
_319
});
_319
}
_319
_319
- (void)chatClient:(TwilioChatClient *)client
_319
channel:(TCHChannel *)channel
_319
memberJoined:(TCHMember *)member {
_319
[self addMessages:@[[StatusEntry statusEntryWithMember:member status:TWCMemberStatusJoined]]];
_319
}
_319
_319
- (void)chatClient:(TwilioChatClient *)client
_319
channel:(TCHChannel *)channel
_319
memberLeft:(TCHMember *)member {
_319
[self addMessages:@[[StatusEntry statusEntryWithMember:member status:TWCMemberStatusLeft]]];
_319
}
_319
_319
- (void)chatClient:(TwilioChatClient *)client channel:(TCHChannel *)channel synchronizationStatusChanged:(TCHChannelSynchronizationStatus)status {
_319
if (status == TCHChannelSynchronizationStatusAll) {
_319
[self loadMessages];
_319
dispatch_async(dispatch_get_main_queue(), ^{
_319
[self.tableView reloadData];
_319
[self setViewOnHold:NO];
_319
});
_319
}
_319
}
_319
_319
#pragma mark - Actions
_319
_319
- (IBAction)actionButtonTouched:(UIBarButtonItem *)sender {
_319
[self leaveChannel];
_319
}
_319
_319
- (IBAction)revealButtonTouched:(UIBarButtonItem *)sender {
_319
[self.revealViewController revealToggleAnimated:YES];
_319
}
_319
_319
@end

If we can join other channels, we'll need some way for a super user to create new channels (and delete old ones).


We use an input dialog so the user can type the name of the new channel. The only restriction here is that the user can't create a channel called "General Channel". Other than that, creating a channel is as simple as calling createChannelWithOptions and passing a dictionary with the new channel information.

twiliochat/ChannelManager.m


_190
#import "ChannelManager.h"
_190
#import "MessagingManager.h"
_190
_190
#define _ Underscore
_190
_190
@interface ChannelManager ()
_190
@property (strong, nonatomic) TCHChannel *generalChannel;
_190
@end
_190
_190
static NSString * const TWCDefaultChannelUniqueName = @"general";
_190
static NSString * const TWCDefaultChannelName = @"General Channel";
_190
_190
static NSString * const TWCFriendlyNameKey = @"friendlyName";
_190
_190
@implementation ChannelManager
_190
_190
+ (instancetype)sharedManager {
_190
static ChannelManager *sharedMyManager = nil;
_190
static dispatch_once_t onceToken;
_190
dispatch_once(&onceToken, ^{
_190
sharedMyManager = [[self alloc] init];
_190
});
_190
return sharedMyManager;
_190
}
_190
_190
- (instancetype)init {
_190
self.channels = [[NSMutableOrderedSet alloc] init];
_190
return self;
_190
}
_190
_190
#pragma mark General channel
_190
_190
- (void)joinGeneralChatRoomWithCompletion:(SucceedHandler)completion {
_190
[self.channelsList channelWithSidOrUniqueName:TWCDefaultChannelUniqueName completion:^(TCHResult *result, TCHChannel *channel) {
_190
if ([result isSuccessful]) {
_190
self.generalChannel = channel;
_190
}
_190
_190
if (self.generalChannel) {
_190
[self joinGeneralChatRoomWithUniqueName:nil completion:completion];
_190
}
_190
else {
_190
[self createGeneralChatRoomWithCompletion:^(BOOL succeeded) {
_190
if (succeeded) {
_190
[self joinGeneralChatRoomWithUniqueName:TWCDefaultChannelUniqueName completion:completion];
_190
return;
_190
}
_190
if (completion) completion(NO);
_190
}];
_190
};
_190
}];
_190
}
_190
_190
- (void)joinGeneralChatRoomWithUniqueName:(NSString *)uniqueName completion:(SucceedHandler)completion {
_190
[self.generalChannel joinWithCompletion:^(TCHResult *result) {
_190
if ([result isSuccessful]) {
_190
if (uniqueName) {
_190
[self setGeneralChatRoomUniqueNameWithCompletion:completion];
_190
return;
_190
}
_190
}
_190
if (completion) completion([result isSuccessful]);
_190
}];
_190
}
_190
_190
- (void)createGeneralChatRoomWithCompletion:(SucceedHandler)completion {
_190
NSDictionary *options = [
_190
NSDictionary
_190
dictionaryWithObjectsAndKeys:TWCDefaultChannelName,
_190
TCHChannelOptionFriendlyName,
_190
TCHChannelTypePublic,
_190
TCHChannelOptionType,
_190
nil
_190
];
_190
_190
[self.channelsList createChannelWithOptions:options
_190
completion:^(TCHResult *result, TCHChannel *channel) {
_190
if ([result isSuccessful]) {
_190
self.generalChannel = channel;
_190
}
_190
if (completion) completion([result isSuccessful]);
_190
}];
_190
}
_190
_190
- (void)setGeneralChatRoomUniqueNameWithCompletion:(SucceedHandler)completion {
_190
[self.generalChannel setUniqueName:TWCDefaultChannelUniqueName
_190
completion:^(TCHResult *result) {
_190
if (completion) completion([result isSuccessful]);
_190
}];
_190
}
_190
_190
#pragma mark Populate channels
_190
_190
- (void)populateChannels {
_190
self.channels = [[NSMutableOrderedSet alloc] init];
_190
[self.channelsList userChannelDescriptorsWithCompletion:^(TCHResult * _Nonnull result, TCHChannelDescriptorPaginator * _Nullable channelPaginator) {
_190
[self.channels addObjectsFromArray:[channelPaginator items]];
_190
[self sortAndDedupeChannels];
_190
if (self.delegate) {
_190
[self.delegate reloadChannelList];
_190
}
_190
}];
_190
_190
[self.channelsList publicChannelDescriptorsWithCompletion:^(TCHResult *result,
_190
TCHChannelDescriptorPaginator *channelDescPaginator) {
_190
[self.channels addObjectsFromArray: [channelDescPaginator items]];
_190
[self sortAndDedupeChannels];
_190
if (self.delegate) {
_190
[self.delegate reloadChannelList];
_190
}
_190
}];
_190
}
_190
_190
- (void)sortAndDedupeChannels {
_190
NSMutableDictionary *channelsDict = [[NSMutableDictionary alloc] init];
_190
_190
for(TCHChannel *channel in self.channels) {
_190
if (![channelsDict objectForKey: channel.sid] ||
_190
![[channelsDict objectForKey: channel.sid] isKindOfClass: [NSNull class]]) {
_190
[channelsDict setObject:channel forKey:channel.sid];
_190
}
_190
}
_190
_190
NSMutableOrderedSet *dedupedChannels = [NSMutableOrderedSet
_190
orderedSetWithArray:[channelsDict allValues]];
_190
_190
SEL sortSelector = @selector(localizedCaseInsensitiveCompare:);
_190
_190
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:TWCFriendlyNameKey
_190
ascending:YES
_190
selector:sortSelector];
_190
_190
[dedupedChannels sortUsingDescriptors:@[descriptor]];
_190
_190
self.channels = dedupedChannels;
_190
}
_190
_190
# pragma mark Create channel
_190
_190
- (void)createChannelWithName:(NSString *)name completion:(ChannelHandler)completion {
_190
if ([name isEqualToString:TWCDefaultChannelName]) {
_190
if (completion) completion(NO, nil);
_190
return;
_190
}
_190
_190
NSDictionary *options = [
_190
NSDictionary
_190
dictionaryWithObjectsAndKeys:name,
_190
TCHChannelOptionFriendlyName,
_190
TCHChannelTypePublic,
_190
TCHChannelOptionType,
_190
nil
_190
];
_190
[self.channelsList
_190
createChannelWithOptions:options
_190
completion:^(TCHResult *result, TCHChannel *channel) {
_190
[self.channels addObject:channel];
_190
[self sortAndDedupeChannels];
_190
if (completion) completion([result isSuccessful], channel);
_190
}];
_190
}
_190
_190
# pragma mark TwilioChatClientDelegate
_190
_190
- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel{
_190
dispatch_async(dispatch_get_main_queue(), ^{
_190
[self.channels addObject:channel];
_190
[self sortAndDedupeChannels];
_190
[self.delegate chatClient:client channelAdded:channel];
_190
});
_190
}
_190
_190
- (void)chatClient:(TwilioChatClient *)client channel:(nonnull TCHChannel *)channel updated:(TCHChannelUpdate)updated {
_190
dispatch_async(dispatch_get_main_queue(), ^{
_190
[self.delegate chatClient:client channel:channel updated:updated];
_190
});
_190
}
_190
_190
- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
_190
dispatch_async(dispatch_get_main_queue(), ^{
_190
[[ChannelManager sharedManager].channels removeObject:channel];
_190
[self.delegate chatClient:client channelDeleted:channel];
_190
});
_190
}
_190
_190
- (void)chatClient:(TwilioChatClient *)client synchronizationStatusUpdated:(TCHClientSynchronizationStatus)status {
_190
_190
}
_190
_190
@end

Cool, we now know how to create a channel, let's say that we created a lot of channels by mistake. In that case, it would be useful to be able to delete those unnecessary channels. That's our next step!


Deleting a channel is easier than creating one. We'll use the UITableView ability to delete a cell. Once you have figured out what channel is meant to be deleted (from the selected cell index path), deleting it is as simple as calling the channel's method destroyWithCompletion.

twiliochat/MenuViewController.m


_222
#import <SWRevealViewController/SWRevealViewController.h>
_222
#import "MenuViewController.h"
_222
#import "MenuTableCell.h"
_222
#import "InputDialogController.h"
_222
#import "MainChatViewController.h"
_222
#import "MessagingManager.h"
_222
#import "AlertDialogController.h"
_222
#import "ChannelManager.h"
_222
#import "SessionManager.h"
_222
_222
@interface MenuViewController ()
_222
@property (weak, nonatomic) IBOutlet UILabel *usernameLabel;
_222
@property (weak, nonatomic) IBOutlet UITableView *tableView;
_222
@property (strong, nonatomic) UIRefreshControl *refreshControl;
_222
_222
@property (strong, nonatomic) TCHChannel *recentlyAddedChannel;
_222
@end
_222
_222
static NSString * const TWCOpenChannelSegue = @"OpenChat";
_222
static NSInteger const TWCRefreshControlXOffset = 120;
_222
_222
_222
@implementation MenuViewController
_222
_222
#pragma mark Initialization
_222
_222
- (void)viewDidLoad {
_222
[super viewDidLoad];
_222
_222
UIImageView *bgImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"home-bg"]];
_222
bgImage.frame = self.tableView.frame;
_222
self.tableView.backgroundView = bgImage;
_222
_222
self.usernameLabel.text = [SessionManager getUsername];
_222
_222
self.refreshControl = [[UIRefreshControl alloc] init];
_222
[self.tableView addSubview:self.refreshControl];
_222
[self.refreshControl addTarget:self
_222
action:@selector(refreshChannels)
_222
forControlEvents:UIControlEventValueChanged];
_222
self.refreshControl.tintColor = [UIColor whiteColor];
_222
_222
CGRect frame = self.refreshControl.frame;
_222
frame.origin.x = CGRectGetMinX(frame) - TWCRefreshControlXOffset;
_222
self.refreshControl.frame = frame;
_222
_222
[ChannelManager sharedManager].delegate = self;
_222
[self reloadChannelList];
_222
}
_222
_222
#pragma mark - Table view data source
_222
_222
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
_222
if (![ChannelManager sharedManager].channels) {
_222
return 1;
_222
}
_222
_222
return [ChannelManager sharedManager].channels.count;
_222
}
_222
_222
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
_222
UITableViewCell *cell = nil;
_222
_222
if (![ChannelManager sharedManager].channels) {
_222
cell = [self loadingCellForTableView:tableView];
_222
}
_222
else {
_222
cell = [self channelCellForTableView:tableView atIndexPath:indexPath];
_222
}
_222
[cell layoutIfNeeded];
_222
_222
return cell;
_222
}
_222
_222
#pragma mark - Table view delegate
_222
_222
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
_222
TCHChannel *channel = [[ChannelManager sharedManager].channels objectAtIndex:indexPath.row];
_222
return channel != [ChannelManager sharedManager].generalChannel;
_222
}
_222
_222
_222
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
_222
if (editingStyle == UITableViewCellEditingStyleDelete) {
_222
TCHChannel *channel = [[ChannelManager sharedManager].channels objectAtIndex:indexPath.row];
_222
[channel destroyWithCompletion:^(TCHResult *result) {
_222
if ([result isSuccessful]) {
_222
[tableView reloadData];
_222
}
_222
else {
_222
[AlertDialogController showAlertWithMessage:@"You can not delete this channel" title:nil presenter:self];
_222
}
_222
}];
_222
}
_222
}
_222
_222
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
_222
[self performSegueWithIdentifier:TWCOpenChannelSegue sender:indexPath];
_222
}
_222
_222
#pragma mark - Internal methods
_222
_222
- (UITableViewCell *)loadingCellForTableView:(UITableView *)tableView {
_222
return [tableView dequeueReusableCellWithIdentifier:@"loadingCell"];
_222
}
_222
_222
- (UITableViewCell *)channelCellForTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
_222
MenuTableCell *menuCell = (MenuTableCell *)[tableView dequeueReusableCellWithIdentifier:@"channelCell" forIndexPath:indexPath];
_222
_222
TCHChannel *channel = [[ChannelManager sharedManager].channels objectAtIndex:indexPath.row];
_222
NSString *friendlyName = channel.friendlyName;
_222
if (channel.friendlyName.length == 0) {
_222
friendlyName = @"(no friendly name)";
_222
}
_222
menuCell.channelName = friendlyName;
_222
_222
return menuCell;
_222
}
_222
_222
- (void)reloadChannelList {
_222
[self.tableView reloadData];
_222
[self.refreshControl endRefreshing];
_222
}
_222
_222
- (void)refreshChannels {
_222
[self.refreshControl beginRefreshing];
_222
[self reloadChannelList];
_222
}
_222
_222
- (void)deselectSelectedChannel {
_222
NSIndexPath *selectedRow = [self.tableView indexPathForSelectedRow];
_222
_222
if (selectedRow) {
_222
[self.tableView deselectRowAtIndexPath:selectedRow animated:YES];
_222
}
_222
}
_222
_222
#pragma mark - Channel
_222
_222
- (void)createNewChannelDialog {
_222
[InputDialogController showWithTitle:@"New Channel"
_222
message:@"Enter a name for this channel."
_222
placeholder:@"Name"
_222
presenter:self handler:^(NSString *text) {
_222
[[ChannelManager sharedManager] createChannelWithName:text completion:^(BOOL success, TCHChannel *channel) {
_222
if (success) {
_222
[self refreshChannels];
_222
}
_222
}];
_222
}];
_222
}
_222
_222
#pragma mark - TwilioChatClientDelegate delegate
_222
_222
- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel {
_222
[self.tableView reloadData];
_222
}
_222
_222
- (void)chatClient:(TwilioChatClient *)client channelChanged:(TCHChannel *)channel {
_222
[self.tableView reloadData];
_222
}
_222
_222
- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
_222
[self.tableView reloadData];
_222
}
_222
_222
#pragma mark - Logout
_222
_222
- (void)promtpLogout {
_222
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
_222
message:@"You are about to Logout." preferredStyle:UIAlertControllerStyleAlert];
_222
_222
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel"
_222
style:UIAlertActionStyleCancel handler:nil];
_222
_222
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"Confirm"
_222
style:UIAlertActionStyleDefault
_222
handler:^(UIAlertAction *action) {
_222
[self logOut];
_222
}];
_222
_222
[alert addAction:cancelAction];
_222
[alert addAction:confirmAction];
_222
[self presentViewController:alert animated:YES completion:nil];
_222
}
_222
_222
- (void)logOut {
_222
[[MessagingManager sharedManager] logout];
_222
[[MessagingManager sharedManager] presentRootViewController];
_222
}
_222
_222
#pragma mark Actions
_222
_222
- (IBAction)logoutButtonTouched:(UIButton *)sender {
_222
[self promtpLogout];
_222
}
_222
_222
- (IBAction)newChannelButtonTouched:(UIButton *)sender {
_222
[self createNewChannelDialog];
_222
}
_222
_222
#pragma mark - Navigation
_222
_222
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
_222
if ([segue.identifier isEqualToString:TWCOpenChannelSegue]) {
_222
NSIndexPath *indexPath = (NSIndexPath *)sender;
_222
_222
TCHChannel *channel = [[ChannelManager sharedManager].channels objectAtIndex:indexPath.row];
_222
UINavigationController *navigationController = [segue destinationViewController];
_222
MainChatViewController *chatViewController = (MainChatViewController *)[navigationController visibleViewController];
_222
chatViewController.channel = channel;
_222
}
_222
}
_222
_222
#pragma mark Style
_222
_222
- (UIStatusBarStyle)preferredStatusBarStyle {
_222
return UIStatusBarStyleLightContent;
_222
}
_222
_222
_222
@end

That's it! We've built an iOS application with Objective-C. Now you are more than prepared to set up your own chat application.


If you are an iOS developer working with Twilio, you might want to check out this other project:

Notifications Quickstart(link takes you to an external page)

Twilio Notifications for iOS Quickstart using Swift

Did this help?

did-this-help page anchor

Thanks for checking out this tutorial! If you have any feedback to share with us, we'd love to hear it. Tweet @twilio(link takes you to an external page) to let us know what you think.


Rate this page: