As the Programmable Chat API is set to sunset in 2022, we will no longer maintain these chat tutorials.
Please see our Conversations API QuickStart to start building robust virtual spaces for conversation.
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.
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:
For your convenience, we consolidated the source code for this tutorial in a single GitHub repository. Feel free to clone it and tweak it as required.
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 to make a request to our server and get the access token.
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_210static NSString * const TWCLoginViewControllerName = @"LoginViewController";_210static NSString * const TWCMainViewControllerName = @"RevealViewController";_210_210static 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.
The synchronizationStatusUpdated
delegate 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 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() 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_210static NSString * const TWCLoginViewControllerName = @"LoginViewController";_210static NSString * const TWCMainViewControllerName = @"RevealViewController";_210_210static 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 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_190static NSString * const TWCDefaultChannelUniqueName = @"general";_190static NSString * const TWCDefaultChannelName = @"General Channel";_190_190static 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
. 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.
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_190static NSString * const TWCDefaultChannelUniqueName = @"general";_190static NSString * const TWCDefaultChannelName = @"General Channel";_190_190static 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.
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.
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_190static NSString * const TWCDefaultChannelUniqueName = @"general";_190static NSString * const TWCDefaultChannelName = @"General Channel";_190_190static 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.
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_319static NSString * const TWCChatCellIdentifier = @"ChatTableCell";_319static NSString * const TWCChatStatusCellIdentifier = @"ChatStatusTableCell";_319_319static NSString * const TWCOpenGeneralChannelSegue = @"OpenGeneralChat";_319static 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 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_319static NSString * const TWCChatCellIdentifier = @"ChatTableCell";_319static NSString * const TWCChatStatusCellIdentifier = @"ChatStatusTableCell";_319_319static NSString * const TWCOpenGeneralChannelSegue = @"OpenGeneralChat";_319static 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_190static NSString * const TWCDefaultChannelUniqueName = @"general";_190static NSString * const TWCDefaultChannelName = @"General Channel";_190_190static 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_222static NSString * const TWCOpenChannelSegue = @"OpenChat";_222static 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:
Twilio Notifications for iOS Quickstart using Swift
Thanks for checking out this tutorial! If you have any feedback to share with us, we'd love to hear it. Tweet @twilio to let us know what you think.