# Chat with iOS and Objective-C

> \[!WARNING]
>
> As the Programmable Chat API is set to [sunset in 2022](https://www.twilio.com/en-us/changelog/programmable-chat-end-of-life-notice), we will no longer maintain these chat tutorials.
>
> Please see our [Conversations API QuickStart](/docs/conversations/quickstart) to start building robust virtual spaces for conversation.

> \[!CAUTION]
>
> 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](https://www.twilio.com/en-us/changelog/programmable-chat-end-of-life-notice).
>
> If you're starting a new project, please visit the [Conversations Docs](/docs/conversations) to begin. If you've already built on Programmable Chat, please visit our [Migration Guide](/docs/conversations/migrating-chat-conversations) 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](https://www.twilio.com/en-us/messaging/conversations-api) 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](/docs/chat/identity#server-create-an-access-token) 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.](https://customers.twilio.com/1234/properati/)*

For your convenience, we consolidated the source code for this tutorial in a single [GitHub repository](https://github.com/TwilioDevEd/twiliochat-objc). Feel free to clone it and tweak it as required.

## Initialize the Chat Client

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:

* [PHP - Laravel](https://github.com/TwilioDevEd/twiliochat-laravel)
* [C# - .NET MVC](https://github.com/TwilioDevEd/twiliochat-csharp)
* [Java - Servlets](https://github.com/TwilioDevEd/twiliochat-servlets)
* [JS - Node](https://github.com/TwilioDevEd/twiliochat-node)

We use [AFNetworking](https://github.com/AFNetworking/AFNetworking) to make a request to our server and get the access token.

```objective-c title="Fetch Access Token" description="twiliochat/MessagingManager.m"
// !mark(100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,94,95,96,97,98,99)
#import "MessagingManager.h"
#import "ChannelManager.h"
#import "SessionManager.h"
#import "TokenRequestHandler.h"

@interface MessagingManager ()
@property (strong, nonatomic) TwilioChatClient *client;
@property (nonatomic, getter=isConnected) BOOL connected;
@end

static NSString * const TWCLoginViewControllerName = @"LoginViewController";
static NSString * const TWCMainViewControllerName = @"RevealViewController";

static NSString * const TWCTokenKey = @"token";

@implementation MessagingManager
+ (instancetype)sharedManager {
  static MessagingManager *sharedMyManager = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedMyManager = [[self alloc] init];
  });
  return sharedMyManager;
}

- (instancetype)init {
  self.delegate = [ChannelManager sharedManager];
  return self;
}

# pragma mark Present view controllers

- (void)presentRootViewController {
  if (!self.isLoggedIn) {
    [self presentViewControllerByName:TWCLoginViewControllerName];
    return;
  }
  if (!self.isConnected) {
    [self connectClientWithCompletion:^(BOOL success, NSError *error) {
      if (success) {
          NSLog(@"Successfully connected chat client");
      }
    }];
  }

}

- (void)presentViewControllerByName:(NSString *)viewController {
  [self presentViewController:[[self storyboardWithName:@"Main"] instantiateViewControllerWithIdentifier:viewController]];
}

- (void)presentLaunchScreen {
  [self presentViewController:[[self storyboardWithName:@"LaunchScreen"] instantiateInitialViewController]];
}

- (void)presentViewController:(UIViewController *)viewController {
  UIWindow *window = [[UIApplication sharedApplication].delegate window];
  window.rootViewController = viewController;
}

- (UIStoryboard *)storyboardWithName:(NSString *)name {
  return [UIStoryboard storyboardWithName: name bundle: [NSBundle mainBundle]];
}

# pragma mark User and session management

- (BOOL)isLoggedIn {
  return [SessionManager isLoggedIn];
}

- (void)loginWithUsername:(NSString *)username
    completion:(StatusWithErrorHandler)completion {
  [SessionManager loginWithUsername:username];
  [self connectClientWithCompletion:^(BOOL success, NSError *error) {
    if (success) {
      [self presentViewControllerByName:TWCMainViewControllerName];
    }
    completion(success, error);
  }];
}

- (void)logout {
  [SessionManager logout];
  self.connected = NO;

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [self.client shutdown];
    self.client = nil;
  });
}

# pragma mark Twilio client

- (void)connectClientWithCompletion:(StatusWithErrorHandler)completion {
  if (self.client) {
    [self logout];
  }

  [self requestTokenWithCompletion:^(BOOL succeeded, NSString *token) {
    if (succeeded) {
      [self initializeClientWithToken:token];
      if (completion) completion(succeeded, nil);
    }
    else {
      NSError *error = [self errorWithDescription:@"Could not get access token" code:301];
      if (completion) completion(succeeded, error);
    }
  }];
}

- (void)initializeClientWithToken:(NSString *)token {
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

  [TwilioChatClient chatClientWithToken:token
                               properties:nil
                                 delegate:self
                             completion:^(TCHResult * _Nonnull result, TwilioChatClient * _Nullable chatClient) {
                                 if (result.isSuccessful) {
                                     self.client = chatClient;
                                     self.connected = YES;

                                 }
                             }];
}

- (void)requestTokenWithCompletion:(StatusWithTokenHandler)completion {
  NSString *uuid = [[UIDevice currentDevice] identifierForVendor].UUIDString;
  NSDictionary *parameters = @{@"device": uuid, @"identity": [SessionManager getUsername]};

  [TokenRequestHandler fetchTokenWithParams:parameters completion:^(NSDictionary *results, NSError *error) {
    NSString *token = [results objectForKey:TWCTokenKey];
    BOOL errorCondition = error || !token;

    if (completion) completion(!errorCondition, token);
  }];
}

- (void)loadGeneralChatRoomWithCompletion:(StatusWithErrorHandler)completion {
  [[ChannelManager sharedManager] joinGeneralChatRoomWithCompletion:^(BOOL succeeded) {
    if (succeeded)
    {
      if (completion) completion(succeeded, nil);
    }
    else {
      NSError *error = [self errorWithDescription:@"Could not join General channel" code:300];
      if (completion) completion(succeeded, error);
    }
  }];
}

- (NSError *)errorWithDescription:(NSString *)description code:(NSInteger)code {
  NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description};
  NSError *error = [NSError errorWithDomain:@"app" code:code userInfo:userInfo];
  return error;
}

#pragma mark Internal helpers

- (NSString *)userIdentity {
  return [SessionManager getUsername];
}

- (void)refreshChatToken:(TwilioChatClient*)client {
    [self requestTokenWithCompletion:^(BOOL succeeded, NSString *token) {
      if (succeeded) {
          [client updateToken:token completion:^(TCHResult * _Nonnull result) {
              if (result.isSuccessful) {

              }
          }];
      }
      else {
        NSLog(@"Error while trying to get new access token");
      }
    }];
}

#pragma mark TwilioChatClientDelegate

- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel {
  [self.delegate chatClient:client channelAdded:channel];
}

- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
  [self.delegate chatClient:client channelDeleted:channel];
}

- (void)chatClient:(TwilioChatClient *)client synchronizationStatusUpdated:(TCHClientSynchronizationStatus)status {
  if (status == TCHClientSynchronizationStatusCompleted) {
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    [ChannelManager sharedManager].channelsList = client.channelsList;
    [[ChannelManager sharedManager] populateChannels];
    [self loadGeneralChatRoomWithCompletion:^(BOOL success, NSError *error) {
        if (success) {
            [self presentViewControllerByName:TWCMainViewControllerName];
        }
    }];
  }
  [self.delegate chatClient:client synchronizationStatusUpdated:status];
}

- (void)chatClientTokenWillExpire:(TwilioChatClient *)client {
    [self refreshChatToken:client];
}

- (void)chatClientTokenExpired:(TwilioChatClient *)client {
    [self refreshChatToken:client];
}

@end

```

Now it's time to synchronize your Twilio client.

## Synchronize the Chat Client

The `synchronizationStatusUpdated` [delegate](https://media.twiliocdn.com/sdk/ios/chat/releases/0.16.0/docs/Protocols/TwilioChatClientDelegate.html) 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](https://media.twiliocdn.com/sdk/ios/chat/releases/0.16.0/docs/Classes/TwilioChatClientProperties.html) 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()](https://media.twiliocdn.com/sdk/ios/chat/releases/0.16.0/docs/Classes/TwilioChatClient.html#//api/name/channelsList) will return `nil`.

```objective-c title="Synchronize the Chat Client" description="twiliochat/MessagingManager.m"
// !mark(188,189,190,191,192,193,194,195,196,197,198,199,200)
#import "MessagingManager.h"
#import "ChannelManager.h"
#import "SessionManager.h"
#import "TokenRequestHandler.h"

@interface MessagingManager ()
@property (strong, nonatomic) TwilioChatClient *client;
@property (nonatomic, getter=isConnected) BOOL connected;
@end

static NSString * const TWCLoginViewControllerName = @"LoginViewController";
static NSString * const TWCMainViewControllerName = @"RevealViewController";

static NSString * const TWCTokenKey = @"token";

@implementation MessagingManager
+ (instancetype)sharedManager {
  static MessagingManager *sharedMyManager = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedMyManager = [[self alloc] init];
  });
  return sharedMyManager;
}

- (instancetype)init {
  self.delegate = [ChannelManager sharedManager];
  return self;
}

# pragma mark Present view controllers

- (void)presentRootViewController {
  if (!self.isLoggedIn) {
    [self presentViewControllerByName:TWCLoginViewControllerName];
    return;
  }
  if (!self.isConnected) {
    [self connectClientWithCompletion:^(BOOL success, NSError *error) {
      if (success) {
          NSLog(@"Successfully connected chat client");
      }
    }];
  }

}

- (void)presentViewControllerByName:(NSString *)viewController {
  [self presentViewController:[[self storyboardWithName:@"Main"] instantiateViewControllerWithIdentifier:viewController]];
}

- (void)presentLaunchScreen {
  [self presentViewController:[[self storyboardWithName:@"LaunchScreen"] instantiateInitialViewController]];
}

- (void)presentViewController:(UIViewController *)viewController {
  UIWindow *window = [[UIApplication sharedApplication].delegate window];
  window.rootViewController = viewController;
}

- (UIStoryboard *)storyboardWithName:(NSString *)name {
  return [UIStoryboard storyboardWithName: name bundle: [NSBundle mainBundle]];
}

# pragma mark User and session management

- (BOOL)isLoggedIn {
  return [SessionManager isLoggedIn];
}

- (void)loginWithUsername:(NSString *)username
    completion:(StatusWithErrorHandler)completion {
  [SessionManager loginWithUsername:username];
  [self connectClientWithCompletion:^(BOOL success, NSError *error) {
    if (success) {
      [self presentViewControllerByName:TWCMainViewControllerName];
    }
    completion(success, error);
  }];
}

- (void)logout {
  [SessionManager logout];
  self.connected = NO;

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [self.client shutdown];
    self.client = nil;
  });
}

# pragma mark Twilio client

- (void)connectClientWithCompletion:(StatusWithErrorHandler)completion {
  if (self.client) {
    [self logout];
  }

  [self requestTokenWithCompletion:^(BOOL succeeded, NSString *token) {
    if (succeeded) {
      [self initializeClientWithToken:token];
      if (completion) completion(succeeded, nil);
    }
    else {
      NSError *error = [self errorWithDescription:@"Could not get access token" code:301];
      if (completion) completion(succeeded, error);
    }
  }];
}

- (void)initializeClientWithToken:(NSString *)token {
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

  [TwilioChatClient chatClientWithToken:token
                               properties:nil
                                 delegate:self
                             completion:^(TCHResult * _Nonnull result, TwilioChatClient * _Nullable chatClient) {
                                 if (result.isSuccessful) {
                                     self.client = chatClient;
                                     self.connected = YES;

                                 }
                             }];
}

- (void)requestTokenWithCompletion:(StatusWithTokenHandler)completion {
  NSString *uuid = [[UIDevice currentDevice] identifierForVendor].UUIDString;
  NSDictionary *parameters = @{@"device": uuid, @"identity": [SessionManager getUsername]};

  [TokenRequestHandler fetchTokenWithParams:parameters completion:^(NSDictionary *results, NSError *error) {
    NSString *token = [results objectForKey:TWCTokenKey];
    BOOL errorCondition = error || !token;

    if (completion) completion(!errorCondition, token);
  }];
}

- (void)loadGeneralChatRoomWithCompletion:(StatusWithErrorHandler)completion {
  [[ChannelManager sharedManager] joinGeneralChatRoomWithCompletion:^(BOOL succeeded) {
    if (succeeded)
    {
      if (completion) completion(succeeded, nil);
    }
    else {
      NSError *error = [self errorWithDescription:@"Could not join General channel" code:300];
      if (completion) completion(succeeded, error);
    }
  }];
}

- (NSError *)errorWithDescription:(NSString *)description code:(NSInteger)code {
  NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description};
  NSError *error = [NSError errorWithDomain:@"app" code:code userInfo:userInfo];
  return error;
}

#pragma mark Internal helpers

- (NSString *)userIdentity {
  return [SessionManager getUsername];
}

- (void)refreshChatToken:(TwilioChatClient*)client {
    [self requestTokenWithCompletion:^(BOOL succeeded, NSString *token) {
      if (succeeded) {
          [client updateToken:token completion:^(TCHResult * _Nonnull result) {
              if (result.isSuccessful) {

              }
          }];
      }
      else {
        NSLog(@"Error while trying to get new access token");
      }
    }];
}

#pragma mark TwilioChatClientDelegate

- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel {
  [self.delegate chatClient:client channelAdded:channel];
}

- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
  [self.delegate chatClient:client channelDeleted:channel];
}

- (void)chatClient:(TwilioChatClient *)client synchronizationStatusUpdated:(TCHClientSynchronizationStatus)status {
  if (status == TCHClientSynchronizationStatusCompleted) {
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    [ChannelManager sharedManager].channelsList = client.channelsList;
    [[ChannelManager sharedManager] populateChannels];
    [self loadGeneralChatRoomWithCompletion:^(BOOL success, NSError *error) {
        if (success) {
            [self presentViewControllerByName:TWCMainViewControllerName];
        }
    }];
  }
  [self.delegate chatClient:client synchronizationStatusUpdated:status];
}

- (void)chatClientTokenWillExpire:(TwilioChatClient *)client {
    [self refreshChatToken:client];
}

- (void)chatClientTokenExpired:(TwilioChatClient *)client {
    [self refreshChatToken:client];
}

@end

```

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

## Get the Channel List

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](https://media.twiliocdn.com/sdk/ios/chat/releases/0.16.0/docs/Classes/TCHChannels.html) to our `ChannelManager`. Now we must get an actual array of channels using the `userChannelsWithCompletion` and `publicChannelsWithCompletion` methods.

```objective-c title="Get Channel List" description="twiliochat/ChannelManager.m"
// !mark(100,101,102,103,104,105,106,107,108,109,110,111,112,113,92,93,94,95,96,97,98,99)
#import "ChannelManager.h"
#import "MessagingManager.h"

#define _ Underscore

@interface ChannelManager ()
@property (strong, nonatomic) TCHChannel *generalChannel;
@end

static NSString * const TWCDefaultChannelUniqueName = @"general";
static NSString * const TWCDefaultChannelName = @"General Channel";

static NSString * const TWCFriendlyNameKey = @"friendlyName";

@implementation ChannelManager

+ (instancetype)sharedManager {
  static ChannelManager *sharedMyManager = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedMyManager = [[self alloc] init];
  });
  return sharedMyManager;
}

- (instancetype)init {
  self.channels = [[NSMutableOrderedSet alloc] init];
  return self;
}

#pragma mark General channel

- (void)joinGeneralChatRoomWithCompletion:(SucceedHandler)completion {
  [self.channelsList channelWithSidOrUniqueName:TWCDefaultChannelUniqueName completion:^(TCHResult *result, TCHChannel *channel) {
    if ([result isSuccessful]) {
      self.generalChannel = channel;
    }

    if (self.generalChannel) {
      [self joinGeneralChatRoomWithUniqueName:nil completion:completion];
    }
    else {
      [self createGeneralChatRoomWithCompletion:^(BOOL succeeded) {
        if (succeeded) {
          [self joinGeneralChatRoomWithUniqueName:TWCDefaultChannelUniqueName completion:completion];
          return;
        }
        if (completion) completion(NO);
      }];
    };
  }];
}

- (void)joinGeneralChatRoomWithUniqueName:(NSString *)uniqueName completion:(SucceedHandler)completion {
  [self.generalChannel joinWithCompletion:^(TCHResult *result) {
    if ([result isSuccessful]) {
      if (uniqueName) {
        [self setGeneralChatRoomUniqueNameWithCompletion:completion];
        return;
      }
    }
    if (completion) completion([result isSuccessful]);
  }];
}

- (void)createGeneralChatRoomWithCompletion:(SucceedHandler)completion {
  NSDictionary *options = [
                           NSDictionary
                           dictionaryWithObjectsAndKeys:TWCDefaultChannelName,
                           TCHChannelOptionFriendlyName,
                           TCHChannelTypePublic,
                           TCHChannelOptionType,
                           nil
                           ];

  [self.channelsList createChannelWithOptions:options
    completion:^(TCHResult *result, TCHChannel *channel) {
      if ([result isSuccessful]) {
        self.generalChannel = channel;
      }
      if (completion) completion([result isSuccessful]);
    }];
}

- (void)setGeneralChatRoomUniqueNameWithCompletion:(SucceedHandler)completion {
  [self.generalChannel setUniqueName:TWCDefaultChannelUniqueName
                          completion:^(TCHResult *result) {
    if (completion) completion([result isSuccessful]);
  }];
}

#pragma mark Populate channels

- (void)populateChannels {
  self.channels = [[NSMutableOrderedSet alloc] init];
    [self.channelsList userChannelDescriptorsWithCompletion:^(TCHResult * _Nonnull result, TCHChannelDescriptorPaginator * _Nullable channelPaginator) {
    [self.channels addObjectsFromArray:[channelPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];

  [self.channelsList publicChannelDescriptorsWithCompletion:^(TCHResult *result,
                                                  TCHChannelDescriptorPaginator *channelDescPaginator) {
    [self.channels addObjectsFromArray: [channelDescPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];
}

- (void)sortAndDedupeChannels {
  NSMutableDictionary *channelsDict = [[NSMutableDictionary alloc] init];

  for(TCHChannel *channel in self.channels) {
    if (![channelsDict objectForKey: channel.sid] ||
        ![[channelsDict objectForKey: channel.sid] isKindOfClass: [NSNull class]]) {
      [channelsDict setObject:channel forKey:channel.sid];
    }
  }

  NSMutableOrderedSet *dedupedChannels = [NSMutableOrderedSet
                                          orderedSetWithArray:[channelsDict allValues]];

  SEL sortSelector = @selector(localizedCaseInsensitiveCompare:);

  NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:TWCFriendlyNameKey
                                                             ascending:YES
                                                              selector:sortSelector];

  [dedupedChannels sortUsingDescriptors:@[descriptor]];

  self.channels = dedupedChannels;
}

# pragma mark Create channel

- (void)createChannelWithName:(NSString *)name completion:(ChannelHandler)completion {
  if ([name isEqualToString:TWCDefaultChannelName]) {
    if (completion) completion(NO, nil);
    return;
  }

  NSDictionary *options = [
                           NSDictionary
                           dictionaryWithObjectsAndKeys:name,
                           TCHChannelOptionFriendlyName,
                           TCHChannelTypePublic,
                           TCHChannelOptionType,
                           nil
                           ];
  [self.channelsList
    createChannelWithOptions:options
    completion:^(TCHResult *result, TCHChannel *channel) {
      [self.channels addObject:channel];
      [self sortAndDedupeChannels];
      if (completion) completion([result isSuccessful], channel);
    }];
}

# pragma mark TwilioChatClientDelegate

- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel{
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.channels addObject:channel];
    [self sortAndDedupeChannels];
    [self.delegate chatClient:client channelAdded:channel];
  });
}

- (void)chatClient:(TwilioChatClient *)client channel:(nonnull TCHChannel *)channel updated:(TCHChannelUpdate)updated {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate chatClient:client channel:channel updated:updated];
  });
}

- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    [[ChannelManager sharedManager].channels removeObject:channel];
    [self.delegate chatClient:client channelDeleted:channel];
  });
}

- (void)chatClient:(TwilioChatClient *)client synchronizationStatusUpdated:(TCHClientSynchronizationStatus)status {

}

@end

```

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

## Listen to Client Events

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`](https://media.twiliocdn.com/sdk/ios/chat/releases/0.16.0/docs/Protocols/TwilioChatClientDelegate.html). 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.

```objective-c title="Listen for Client Events" description="twiliochat/ChannelManager.m"
// !mark(164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188)
#import "ChannelManager.h"
#import "MessagingManager.h"

#define _ Underscore

@interface ChannelManager ()
@property (strong, nonatomic) TCHChannel *generalChannel;
@end

static NSString * const TWCDefaultChannelUniqueName = @"general";
static NSString * const TWCDefaultChannelName = @"General Channel";

static NSString * const TWCFriendlyNameKey = @"friendlyName";

@implementation ChannelManager

+ (instancetype)sharedManager {
  static ChannelManager *sharedMyManager = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedMyManager = [[self alloc] init];
  });
  return sharedMyManager;
}

- (instancetype)init {
  self.channels = [[NSMutableOrderedSet alloc] init];
  return self;
}

#pragma mark General channel

- (void)joinGeneralChatRoomWithCompletion:(SucceedHandler)completion {
  [self.channelsList channelWithSidOrUniqueName:TWCDefaultChannelUniqueName completion:^(TCHResult *result, TCHChannel *channel) {
    if ([result isSuccessful]) {
      self.generalChannel = channel;
    }

    if (self.generalChannel) {
      [self joinGeneralChatRoomWithUniqueName:nil completion:completion];
    }
    else {
      [self createGeneralChatRoomWithCompletion:^(BOOL succeeded) {
        if (succeeded) {
          [self joinGeneralChatRoomWithUniqueName:TWCDefaultChannelUniqueName completion:completion];
          return;
        }
        if (completion) completion(NO);
      }];
    };
  }];
}

- (void)joinGeneralChatRoomWithUniqueName:(NSString *)uniqueName completion:(SucceedHandler)completion {
  [self.generalChannel joinWithCompletion:^(TCHResult *result) {
    if ([result isSuccessful]) {
      if (uniqueName) {
        [self setGeneralChatRoomUniqueNameWithCompletion:completion];
        return;
      }
    }
    if (completion) completion([result isSuccessful]);
  }];
}

- (void)createGeneralChatRoomWithCompletion:(SucceedHandler)completion {
  NSDictionary *options = [
                           NSDictionary
                           dictionaryWithObjectsAndKeys:TWCDefaultChannelName,
                           TCHChannelOptionFriendlyName,
                           TCHChannelTypePublic,
                           TCHChannelOptionType,
                           nil
                           ];

  [self.channelsList createChannelWithOptions:options
    completion:^(TCHResult *result, TCHChannel *channel) {
      if ([result isSuccessful]) {
        self.generalChannel = channel;
      }
      if (completion) completion([result isSuccessful]);
    }];
}

- (void)setGeneralChatRoomUniqueNameWithCompletion:(SucceedHandler)completion {
  [self.generalChannel setUniqueName:TWCDefaultChannelUniqueName
                          completion:^(TCHResult *result) {
    if (completion) completion([result isSuccessful]);
  }];
}

#pragma mark Populate channels

- (void)populateChannels {
  self.channels = [[NSMutableOrderedSet alloc] init];
    [self.channelsList userChannelDescriptorsWithCompletion:^(TCHResult * _Nonnull result, TCHChannelDescriptorPaginator * _Nullable channelPaginator) {
    [self.channels addObjectsFromArray:[channelPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];

  [self.channelsList publicChannelDescriptorsWithCompletion:^(TCHResult *result,
                                                  TCHChannelDescriptorPaginator *channelDescPaginator) {
    [self.channels addObjectsFromArray: [channelDescPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];
}

- (void)sortAndDedupeChannels {
  NSMutableDictionary *channelsDict = [[NSMutableDictionary alloc] init];

  for(TCHChannel *channel in self.channels) {
    if (![channelsDict objectForKey: channel.sid] ||
        ![[channelsDict objectForKey: channel.sid] isKindOfClass: [NSNull class]]) {
      [channelsDict setObject:channel forKey:channel.sid];
    }
  }

  NSMutableOrderedSet *dedupedChannels = [NSMutableOrderedSet
                                          orderedSetWithArray:[channelsDict allValues]];

  SEL sortSelector = @selector(localizedCaseInsensitiveCompare:);

  NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:TWCFriendlyNameKey
                                                             ascending:YES
                                                              selector:sortSelector];

  [dedupedChannels sortUsingDescriptors:@[descriptor]];

  self.channels = dedupedChannels;
}

# pragma mark Create channel

- (void)createChannelWithName:(NSString *)name completion:(ChannelHandler)completion {
  if ([name isEqualToString:TWCDefaultChannelName]) {
    if (completion) completion(NO, nil);
    return;
  }

  NSDictionary *options = [
                           NSDictionary
                           dictionaryWithObjectsAndKeys:name,
                           TCHChannelOptionFriendlyName,
                           TCHChannelTypePublic,
                           TCHChannelOptionType,
                           nil
                           ];
  [self.channelsList
    createChannelWithOptions:options
    completion:^(TCHResult *result, TCHChannel *channel) {
      [self.channels addObject:channel];
      [self sortAndDedupeChannels];
      if (completion) completion([result isSuccessful], channel);
    }];
}

# pragma mark TwilioChatClientDelegate

- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel{
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.channels addObject:channel];
    [self sortAndDedupeChannels];
    [self.delegate chatClient:client channelAdded:channel];
  });
}

- (void)chatClient:(TwilioChatClient *)client channel:(nonnull TCHChannel *)channel updated:(TCHChannelUpdate)updated {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate chatClient:client channel:channel updated:updated];
  });
}

- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    [[ChannelManager sharedManager].channels removeObject:channel];
    [self.delegate chatClient:client channelDeleted:channel];
  });
}

- (void)chatClient:(TwilioChatClient *)client synchronizationStatusUpdated:(TCHClientSynchronizationStatus)status {

}

@end

```

Next, we need a default channel.

## Join the General 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.

```objective-c title="Join or Create a General Channel" description="twiliochat/ChannelManager.m"
// !mark(31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83)
#import "ChannelManager.h"
#import "MessagingManager.h"

#define _ Underscore

@interface ChannelManager ()
@property (strong, nonatomic) TCHChannel *generalChannel;
@end

static NSString * const TWCDefaultChannelUniqueName = @"general";
static NSString * const TWCDefaultChannelName = @"General Channel";

static NSString * const TWCFriendlyNameKey = @"friendlyName";

@implementation ChannelManager

+ (instancetype)sharedManager {
  static ChannelManager *sharedMyManager = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedMyManager = [[self alloc] init];
  });
  return sharedMyManager;
}

- (instancetype)init {
  self.channels = [[NSMutableOrderedSet alloc] init];
  return self;
}

#pragma mark General channel

- (void)joinGeneralChatRoomWithCompletion:(SucceedHandler)completion {
  [self.channelsList channelWithSidOrUniqueName:TWCDefaultChannelUniqueName completion:^(TCHResult *result, TCHChannel *channel) {
    if ([result isSuccessful]) {
      self.generalChannel = channel;
    }

    if (self.generalChannel) {
      [self joinGeneralChatRoomWithUniqueName:nil completion:completion];
    }
    else {
      [self createGeneralChatRoomWithCompletion:^(BOOL succeeded) {
        if (succeeded) {
          [self joinGeneralChatRoomWithUniqueName:TWCDefaultChannelUniqueName completion:completion];
          return;
        }
        if (completion) completion(NO);
      }];
    };
  }];
}

- (void)joinGeneralChatRoomWithUniqueName:(NSString *)uniqueName completion:(SucceedHandler)completion {
  [self.generalChannel joinWithCompletion:^(TCHResult *result) {
    if ([result isSuccessful]) {
      if (uniqueName) {
        [self setGeneralChatRoomUniqueNameWithCompletion:completion];
        return;
      }
    }
    if (completion) completion([result isSuccessful]);
  }];
}

- (void)createGeneralChatRoomWithCompletion:(SucceedHandler)completion {
  NSDictionary *options = [
                           NSDictionary
                           dictionaryWithObjectsAndKeys:TWCDefaultChannelName,
                           TCHChannelOptionFriendlyName,
                           TCHChannelTypePublic,
                           TCHChannelOptionType,
                           nil
                           ];

  [self.channelsList createChannelWithOptions:options
    completion:^(TCHResult *result, TCHChannel *channel) {
      if ([result isSuccessful]) {
        self.generalChannel = channel;
      }
      if (completion) completion([result isSuccessful]);
    }];
}

- (void)setGeneralChatRoomUniqueNameWithCompletion:(SucceedHandler)completion {
  [self.generalChannel setUniqueName:TWCDefaultChannelUniqueName
                          completion:^(TCHResult *result) {
    if (completion) completion([result isSuccessful]);
  }];
}

#pragma mark Populate channels

- (void)populateChannels {
  self.channels = [[NSMutableOrderedSet alloc] init];
    [self.channelsList userChannelDescriptorsWithCompletion:^(TCHResult * _Nonnull result, TCHChannelDescriptorPaginator * _Nullable channelPaginator) {
    [self.channels addObjectsFromArray:[channelPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];

  [self.channelsList publicChannelDescriptorsWithCompletion:^(TCHResult *result,
                                                  TCHChannelDescriptorPaginator *channelDescPaginator) {
    [self.channels addObjectsFromArray: [channelDescPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];
}

- (void)sortAndDedupeChannels {
  NSMutableDictionary *channelsDict = [[NSMutableDictionary alloc] init];

  for(TCHChannel *channel in self.channels) {
    if (![channelsDict objectForKey: channel.sid] ||
        ![[channelsDict objectForKey: channel.sid] isKindOfClass: [NSNull class]]) {
      [channelsDict setObject:channel forKey:channel.sid];
    }
  }

  NSMutableOrderedSet *dedupedChannels = [NSMutableOrderedSet
                                          orderedSetWithArray:[channelsDict allValues]];

  SEL sortSelector = @selector(localizedCaseInsensitiveCompare:);

  NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:TWCFriendlyNameKey
                                                             ascending:YES
                                                              selector:sortSelector];

  [dedupedChannels sortUsingDescriptors:@[descriptor]];

  self.channels = dedupedChannels;
}

# pragma mark Create channel

- (void)createChannelWithName:(NSString *)name completion:(ChannelHandler)completion {
  if ([name isEqualToString:TWCDefaultChannelName]) {
    if (completion) completion(NO, nil);
    return;
  }

  NSDictionary *options = [
                           NSDictionary
                           dictionaryWithObjectsAndKeys:name,
                           TCHChannelOptionFriendlyName,
                           TCHChannelTypePublic,
                           TCHChannelOptionType,
                           nil
                           ];
  [self.channelsList
    createChannelWithOptions:options
    completion:^(TCHResult *result, TCHChannel *channel) {
      [self.channels addObject:channel];
      [self sortAndDedupeChannels];
      if (completion) completion([result isSuccessful], channel);
    }];
}

# pragma mark TwilioChatClientDelegate

- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel{
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.channels addObject:channel];
    [self sortAndDedupeChannels];
    [self.delegate chatClient:client channelAdded:channel];
  });
}

- (void)chatClient:(TwilioChatClient *)client channel:(nonnull TCHChannel *)channel updated:(TCHChannelUpdate)updated {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate chatClient:client channel:channel updated:updated];
  });
}

- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    [[ChannelManager sharedManager].channels removeObject:channel];
    [self.delegate chatClient:client channelDeleted:channel];
  });
}

- (void)chatClient:(TwilioChatClient *)client synchronizationStatusUpdated:(TCHClientSynchronizationStatus)status {

}

@end

```

Now let's listen for some channel events.

## Listen to 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.

```objective-c title="Listen to Channel Events" description="twiliochat/MainChatViewController.m"
// !mark(269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307)
#import <TwilioChatClient/TwilioChatClient.h>
#import "MainChatViewController.h"
#import "ChatTableCell.h"
#import "NSDate+ISO8601Parser.h"
#import "SWRevealViewController.h"
#import "ChannelManager.h"
#import "StatusEntry.h"
#import "DateTodayFormatter.h"
#import "MenuViewController.h"

@interface MainChatViewController ()
@property (weak, nonatomic) IBOutlet UIBarButtonItem *revealButtonItem;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *actionButtonItem;

@property (strong, nonatomic) NSMutableOrderedSet *messages;

@end

static NSString * const TWCChatCellIdentifier = @"ChatTableCell";
static NSString * const TWCChatStatusCellIdentifier = @"ChatStatusTableCell";

static NSString * const TWCOpenGeneralChannelSegue = @"OpenGeneralChat";
static NSInteger const TWCLabelTag = 200;

@implementation MainChatViewController

#pragma mark Initialization

- (void)viewDidLoad {
  [super viewDidLoad];

  if (self.revealViewController)
  {
    [self.revealButtonItem setTarget: self.revealViewController];
    [self.revealButtonItem setAction: @selector( revealToggle: )];
    [self.navigationController.navigationBar addGestureRecognizer: self.revealViewController.panGestureRecognizer];
    self.revealViewController.rearViewRevealOverdraw = 0.f;
  }

  self.bounces = YES;
  self.shakeToClearEnabled = YES;
  self.keyboardPanningEnabled = YES;
  self.shouldScrollToBottomAfterKeyboardShows = NO;
  self.inverted = YES;

  UINib *cellNib = [UINib nibWithNibName:TWCChatCellIdentifier bundle:nil];
  [self.tableView registerNib:cellNib
       forCellReuseIdentifier:TWCChatCellIdentifier];

  UINib *cellStatusNib = [UINib nibWithNibName:TWCChatStatusCellIdentifier bundle:nil];
  [self.tableView registerNib:cellStatusNib
       forCellReuseIdentifier:TWCChatStatusCellIdentifier];

  self.textInputbar.autoHideRightButton = YES;
  self.textInputbar.maxCharCount = 256;
  self.textInputbar.counterStyle = SLKCounterStyleSplit;
  self.textInputbar.counterPosition = SLKCounterPositionTop;

  UIFont *font = [UIFont fontWithName:@"Avenir-Light" size:14];
  self.textView.font = font;

  [self.rightButton setTitleColor:[UIColor colorWithRed:0.973 green:0.557 blue:0.502 alpha:1]
                         forState:UIControlStateNormal];

  font = [UIFont fontWithName:@"Avenir-Heavy" size:17];
  self.navigationController.navigationBar.titleTextAttributes = @{NSFontAttributeName:font};

  self.tableView.allowsSelection = NO;
  self.tableView.estimatedRowHeight = 70;
  self.tableView.rowHeight = UITableViewAutomaticDimension;
  self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;

  if (!self.channel) {
    id generalChannel = [ChannelManager sharedManager].generalChannel;
      if (generalChannel) {
          self.channel = generalChannel;
      } else {
          [[ChannelManager sharedManager] joinGeneralChatRoomWithCompletion:^(BOOL succeeded) {
              if (succeeded) {
                  self.channel = [ChannelManager sharedManager].generalChannel;
              }
          }];
      }
  }
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    [self.textInputbar bringSubviewToFront:self.textInputbar.textView];
    [self.textInputbar bringSubviewToFront:self.textInputbar.leftButton];
    [self.textInputbar bringSubviewToFront:self.textInputbar.rightButton];
}

- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  [self scrollToBottomMessage];
}

- (NSMutableOrderedSet *)messages {
  if (!_messages) {
    _messages = [[NSMutableOrderedSet alloc] init];
  }
  return _messages;
}

- (void)setChannel:(TCHChannel *)channel {
  if ([channel isKindOfClass:[TCHChannelDescriptor class]]) {
    TCHChannelDescriptor *channelDescriptor = (TCHChannelDescriptor*)channel;
    [channelDescriptor channelWithCompletion:^(TCHResult *success, TCHChannel *channel) {
      if (success) {
        [self actuallySetChannel:channel];
      }
    }];
  } else {
    [self actuallySetChannel:channel];
  }
}

- (void)actuallySetChannel:(TCHChannel *)channel {
  _channel = channel;
  self.title = self.channel.friendlyName;
  self.channel.delegate = self;

  if (self.channel == [ChannelManager sharedManager].generalChannel) {
    self.navigationItem.rightBarButtonItem = nil;
  }

  [self setViewOnHold:YES];

  if (self.channel.status != TCHChannelStatusJoined) {
    [self.channel joinWithCompletion:^(TCHResult* result) {
      NSLog(@"%@", @"Channel Joined");
      [self setViewOnHold:NO];
    }];
  }
  if (self.channel.synchronizationStatus == TCHChannelSynchronizationStatusAll) {
    [self loadMessages];
    [self setViewOnHold:NO];
  }
}

// Disable user input and show activity indicator
- (void)setViewOnHold:(BOOL)onHold {
  self.textInputbarHidden = onHold;
  [UIApplication sharedApplication].networkActivityIndicatorVisible = onHold;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  return self.messages.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  UITableViewCell *cell = nil;

  id message = [self.messages objectAtIndex:indexPath.row];

  if ([message isKindOfClass:[TCHMessage class]]) {
    cell = [self getChatCellForTableView:tableView forIndexPath:indexPath message:message];
  }
  else {
    cell = [self getStatusCellForTableView:tableView forIndexPath:indexPath message:message];
  }

  cell.transform = tableView.transform;
  return cell;
}

- (ChatTableCell *)getChatCellForTableView:(UITableView *)tableView
                              forIndexPath:(NSIndexPath *)indexPath
                                   message:(TCHMessage *)message {
  UITableViewCell *cell = [self.tableView
    dequeueReusableCellWithIdentifier:TWCChatCellIdentifier forIndexPath:indexPath];

  ChatTableCell *chatCell = (ChatTableCell *)cell;
  chatCell.user = message.author;
  chatCell.date = [[[DateTodayFormatter alloc] init]
    stringFromDate:[NSDate dateWithISO8601String:message.timestamp]];

  chatCell.message = message.body;

  return chatCell;
}

- (UITableViewCell *)getStatusCellForTableView:(UITableView *)tableView
                                 forIndexPath:(NSIndexPath *)indexPath
                                      message:(StatusEntry *)message {
  UITableViewCell *cell = [self.tableView
    dequeueReusableCellWithIdentifier:TWCChatStatusCellIdentifier forIndexPath:indexPath];

  UILabel *label = [cell viewWithTag:TWCLabelTag];
  label.text = [NSString stringWithFormat:@"User %@ has %@",
     message.member.identity, (message.status == TWCMemberStatusJoined) ? @"joined" : @"left"];

  return cell;
}

- (void)didPressRightButton:(id)sender {
  [self.textView refreshFirstResponder];
  [self sendMessage: [self.textView.text copy]];
  [super didPressRightButton:sender];
}

#pragma mark Chat Service
- (void)sendMessage: (NSString *)inputMessage {
    TCHMessageOptions *messageOptions = [[[TCHMessageOptions alloc] init] withBody:inputMessage];
    [self.channel.messages sendMessageWithOptions:messageOptions
                                       completion:nil];
}



- (void)addMessages:(NSArray *)messages {
  [self.messages addObjectsFromArray:messages];
  [self sortMessages];
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.tableView reloadData];
    if (self.messages.count > 0) {
      [self scrollToBottomMessage];
    }
  });
}


- (void)sortMessages {
  [self.messages sortUsingDescriptors:@[[[NSSortDescriptor alloc]
    initWithKey:@"timestamp" ascending:NO]]];
}

- (void)scrollToBottomMessage {
  if (self.messages.count == 0) {
    return;
  }

  NSIndexPath *bottomMessageIndex = [NSIndexPath indexPathForRow:0
                                                       inSection:0];
  [self.tableView scrollToRowAtIndexPath:bottomMessageIndex
    atScrollPosition:UITableViewScrollPositionBottom animated:NO];
}

- (void)loadMessages {
  [self.messages removeAllObjects];
  if (self.channel.synchronizationStatus == TCHChannelSynchronizationStatusAll) {
    [self.channel.messages
     getLastMessagesWithCount:100
     completion:^(TCHResult *result, NSArray *messages) {
      if ([result isSuccessful]) {
        [self addMessages: messages];
      }
    }];
  }
}

- (void)leaveChannel {
  [self.channel leaveWithCompletion:^(TCHResult* result) {
    if ([result isSuccessful]) {
      [(MenuViewController *)self.revealViewController.rearViewController deselectSelectedChannel];
      [self.revealViewController.rearViewController
        performSegueWithIdentifier:TWCOpenGeneralChannelSegue sender:nil];
    }
  }];
}

#pragma mark - TMMessageDelegate

- (void)chatClient:(TwilioChatClient *)client
                  channel:(TCHChannel *)channel
             messageAdded:(TCHMessage *)message {
  if (![self.messages containsObject:message]) {
    [self addMessages:@[message]];
  }
}

- (void)chatClient:(TwilioChatClient *)client
           channelDeleted:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    if (channel == self.channel) {
      [self.revealViewController.rearViewController
        performSegueWithIdentifier:TWCOpenGeneralChannelSegue sender:nil];
    }
  });
}

- (void)chatClient:(TwilioChatClient *)client
                  channel:(TCHChannel *)channel
             memberJoined:(TCHMember *)member {
  [self addMessages:@[[StatusEntry statusEntryWithMember:member status:TWCMemberStatusJoined]]];
}

- (void)chatClient:(TwilioChatClient *)client
                  channel:(TCHChannel *)channel
               memberLeft:(TCHMember *)member {
  [self addMessages:@[[StatusEntry statusEntryWithMember:member status:TWCMemberStatusLeft]]];
}

- (void)chatClient:(TwilioChatClient *)client channel:(TCHChannel *)channel synchronizationStatusChanged:(TCHChannelSynchronizationStatus)status {
  if (status == TCHChannelSynchronizationStatusAll) {
    [self loadMessages];
    dispatch_async(dispatch_get_main_queue(), ^{
      [self.tableView reloadData];
      [self setViewOnHold:NO];
    });
  }
}

#pragma mark - Actions

- (IBAction)actionButtonTouched:(UIBarButtonItem *)sender {
  [self leaveChannel];
}

- (IBAction)revealButtonTouched:(UIBarButtonItem *)sender {
  [self.revealViewController revealToggleAnimated:YES];
}

@end

```

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

## Join Other Channels

The application uses [SWRevealViewController](https://github.com/John-Lluch/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.

```objective-c title="Join Other Channels" description="twiliochat/MainChatViewController.m"
// !mark(106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140)
#import <TwilioChatClient/TwilioChatClient.h>
#import "MainChatViewController.h"
#import "ChatTableCell.h"
#import "NSDate+ISO8601Parser.h"
#import "SWRevealViewController.h"
#import "ChannelManager.h"
#import "StatusEntry.h"
#import "DateTodayFormatter.h"
#import "MenuViewController.h"

@interface MainChatViewController ()
@property (weak, nonatomic) IBOutlet UIBarButtonItem *revealButtonItem;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *actionButtonItem;

@property (strong, nonatomic) NSMutableOrderedSet *messages;

@end

static NSString * const TWCChatCellIdentifier = @"ChatTableCell";
static NSString * const TWCChatStatusCellIdentifier = @"ChatStatusTableCell";

static NSString * const TWCOpenGeneralChannelSegue = @"OpenGeneralChat";
static NSInteger const TWCLabelTag = 200;

@implementation MainChatViewController

#pragma mark Initialization

- (void)viewDidLoad {
  [super viewDidLoad];

  if (self.revealViewController)
  {
    [self.revealButtonItem setTarget: self.revealViewController];
    [self.revealButtonItem setAction: @selector( revealToggle: )];
    [self.navigationController.navigationBar addGestureRecognizer: self.revealViewController.panGestureRecognizer];
    self.revealViewController.rearViewRevealOverdraw = 0.f;
  }

  self.bounces = YES;
  self.shakeToClearEnabled = YES;
  self.keyboardPanningEnabled = YES;
  self.shouldScrollToBottomAfterKeyboardShows = NO;
  self.inverted = YES;

  UINib *cellNib = [UINib nibWithNibName:TWCChatCellIdentifier bundle:nil];
  [self.tableView registerNib:cellNib
       forCellReuseIdentifier:TWCChatCellIdentifier];

  UINib *cellStatusNib = [UINib nibWithNibName:TWCChatStatusCellIdentifier bundle:nil];
  [self.tableView registerNib:cellStatusNib
       forCellReuseIdentifier:TWCChatStatusCellIdentifier];

  self.textInputbar.autoHideRightButton = YES;
  self.textInputbar.maxCharCount = 256;
  self.textInputbar.counterStyle = SLKCounterStyleSplit;
  self.textInputbar.counterPosition = SLKCounterPositionTop;

  UIFont *font = [UIFont fontWithName:@"Avenir-Light" size:14];
  self.textView.font = font;

  [self.rightButton setTitleColor:[UIColor colorWithRed:0.973 green:0.557 blue:0.502 alpha:1]
                         forState:UIControlStateNormal];

  font = [UIFont fontWithName:@"Avenir-Heavy" size:17];
  self.navigationController.navigationBar.titleTextAttributes = @{NSFontAttributeName:font};

  self.tableView.allowsSelection = NO;
  self.tableView.estimatedRowHeight = 70;
  self.tableView.rowHeight = UITableViewAutomaticDimension;
  self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;

  if (!self.channel) {
    id generalChannel = [ChannelManager sharedManager].generalChannel;
      if (generalChannel) {
          self.channel = generalChannel;
      } else {
          [[ChannelManager sharedManager] joinGeneralChatRoomWithCompletion:^(BOOL succeeded) {
              if (succeeded) {
                  self.channel = [ChannelManager sharedManager].generalChannel;
              }
          }];
      }
  }
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    [self.textInputbar bringSubviewToFront:self.textInputbar.textView];
    [self.textInputbar bringSubviewToFront:self.textInputbar.leftButton];
    [self.textInputbar bringSubviewToFront:self.textInputbar.rightButton];
}

- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  [self scrollToBottomMessage];
}

- (NSMutableOrderedSet *)messages {
  if (!_messages) {
    _messages = [[NSMutableOrderedSet alloc] init];
  }
  return _messages;
}

- (void)setChannel:(TCHChannel *)channel {
  if ([channel isKindOfClass:[TCHChannelDescriptor class]]) {
    TCHChannelDescriptor *channelDescriptor = (TCHChannelDescriptor*)channel;
    [channelDescriptor channelWithCompletion:^(TCHResult *success, TCHChannel *channel) {
      if (success) {
        [self actuallySetChannel:channel];
      }
    }];
  } else {
    [self actuallySetChannel:channel];
  }
}

- (void)actuallySetChannel:(TCHChannel *)channel {
  _channel = channel;
  self.title = self.channel.friendlyName;
  self.channel.delegate = self;

  if (self.channel == [ChannelManager sharedManager].generalChannel) {
    self.navigationItem.rightBarButtonItem = nil;
  }

  [self setViewOnHold:YES];

  if (self.channel.status != TCHChannelStatusJoined) {
    [self.channel joinWithCompletion:^(TCHResult* result) {
      NSLog(@"%@", @"Channel Joined");
      [self setViewOnHold:NO];
    }];
  }
  if (self.channel.synchronizationStatus == TCHChannelSynchronizationStatusAll) {
    [self loadMessages];
    [self setViewOnHold:NO];
  }
}

// Disable user input and show activity indicator
- (void)setViewOnHold:(BOOL)onHold {
  self.textInputbarHidden = onHold;
  [UIApplication sharedApplication].networkActivityIndicatorVisible = onHold;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  return self.messages.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  UITableViewCell *cell = nil;

  id message = [self.messages objectAtIndex:indexPath.row];

  if ([message isKindOfClass:[TCHMessage class]]) {
    cell = [self getChatCellForTableView:tableView forIndexPath:indexPath message:message];
  }
  else {
    cell = [self getStatusCellForTableView:tableView forIndexPath:indexPath message:message];
  }

  cell.transform = tableView.transform;
  return cell;
}

- (ChatTableCell *)getChatCellForTableView:(UITableView *)tableView
                              forIndexPath:(NSIndexPath *)indexPath
                                   message:(TCHMessage *)message {
  UITableViewCell *cell = [self.tableView
    dequeueReusableCellWithIdentifier:TWCChatCellIdentifier forIndexPath:indexPath];

  ChatTableCell *chatCell = (ChatTableCell *)cell;
  chatCell.user = message.author;
  chatCell.date = [[[DateTodayFormatter alloc] init]
    stringFromDate:[NSDate dateWithISO8601String:message.timestamp]];

  chatCell.message = message.body;

  return chatCell;
}

- (UITableViewCell *)getStatusCellForTableView:(UITableView *)tableView
                                 forIndexPath:(NSIndexPath *)indexPath
                                      message:(StatusEntry *)message {
  UITableViewCell *cell = [self.tableView
    dequeueReusableCellWithIdentifier:TWCChatStatusCellIdentifier forIndexPath:indexPath];

  UILabel *label = [cell viewWithTag:TWCLabelTag];
  label.text = [NSString stringWithFormat:@"User %@ has %@",
     message.member.identity, (message.status == TWCMemberStatusJoined) ? @"joined" : @"left"];

  return cell;
}

- (void)didPressRightButton:(id)sender {
  [self.textView refreshFirstResponder];
  [self sendMessage: [self.textView.text copy]];
  [super didPressRightButton:sender];
}

#pragma mark Chat Service
- (void)sendMessage: (NSString *)inputMessage {
    TCHMessageOptions *messageOptions = [[[TCHMessageOptions alloc] init] withBody:inputMessage];
    [self.channel.messages sendMessageWithOptions:messageOptions
                                       completion:nil];
}



- (void)addMessages:(NSArray *)messages {
  [self.messages addObjectsFromArray:messages];
  [self sortMessages];
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.tableView reloadData];
    if (self.messages.count > 0) {
      [self scrollToBottomMessage];
    }
  });
}


- (void)sortMessages {
  [self.messages sortUsingDescriptors:@[[[NSSortDescriptor alloc]
    initWithKey:@"timestamp" ascending:NO]]];
}

- (void)scrollToBottomMessage {
  if (self.messages.count == 0) {
    return;
  }

  NSIndexPath *bottomMessageIndex = [NSIndexPath indexPathForRow:0
                                                       inSection:0];
  [self.tableView scrollToRowAtIndexPath:bottomMessageIndex
    atScrollPosition:UITableViewScrollPositionBottom animated:NO];
}

- (void)loadMessages {
  [self.messages removeAllObjects];
  if (self.channel.synchronizationStatus == TCHChannelSynchronizationStatusAll) {
    [self.channel.messages
     getLastMessagesWithCount:100
     completion:^(TCHResult *result, NSArray *messages) {
      if ([result isSuccessful]) {
        [self addMessages: messages];
      }
    }];
  }
}

- (void)leaveChannel {
  [self.channel leaveWithCompletion:^(TCHResult* result) {
    if ([result isSuccessful]) {
      [(MenuViewController *)self.revealViewController.rearViewController deselectSelectedChannel];
      [self.revealViewController.rearViewController
        performSegueWithIdentifier:TWCOpenGeneralChannelSegue sender:nil];
    }
  }];
}

#pragma mark - TMMessageDelegate

- (void)chatClient:(TwilioChatClient *)client
                  channel:(TCHChannel *)channel
             messageAdded:(TCHMessage *)message {
  if (![self.messages containsObject:message]) {
    [self addMessages:@[message]];
  }
}

- (void)chatClient:(TwilioChatClient *)client
           channelDeleted:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    if (channel == self.channel) {
      [self.revealViewController.rearViewController
        performSegueWithIdentifier:TWCOpenGeneralChannelSegue sender:nil];
    }
  });
}

- (void)chatClient:(TwilioChatClient *)client
                  channel:(TCHChannel *)channel
             memberJoined:(TCHMember *)member {
  [self addMessages:@[[StatusEntry statusEntryWithMember:member status:TWCMemberStatusJoined]]];
}

- (void)chatClient:(TwilioChatClient *)client
                  channel:(TCHChannel *)channel
               memberLeft:(TCHMember *)member {
  [self addMessages:@[[StatusEntry statusEntryWithMember:member status:TWCMemberStatusLeft]]];
}

- (void)chatClient:(TwilioChatClient *)client channel:(TCHChannel *)channel synchronizationStatusChanged:(TCHChannelSynchronizationStatus)status {
  if (status == TCHChannelSynchronizationStatusAll) {
    [self loadMessages];
    dispatch_async(dispatch_get_main_queue(), ^{
      [self.tableView reloadData];
      [self setViewOnHold:NO];
    });
  }
}

#pragma mark - Actions

- (IBAction)actionButtonTouched:(UIBarButtonItem *)sender {
  [self leaveChannel];
}

- (IBAction)revealButtonTouched:(UIBarButtonItem *)sender {
  [self.revealViewController revealToggleAnimated:YES];
}

@end

```

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

## Create a Channel

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 involves calling `createChannelWithOptions` and passing a dictionary with the new channel information.

```objective-c title="Create a Channel" description="twiliochat/ChannelManager.m"
// !mark(140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161)
#import "ChannelManager.h"
#import "MessagingManager.h"

#define _ Underscore

@interface ChannelManager ()
@property (strong, nonatomic) TCHChannel *generalChannel;
@end

static NSString * const TWCDefaultChannelUniqueName = @"general";
static NSString * const TWCDefaultChannelName = @"General Channel";

static NSString * const TWCFriendlyNameKey = @"friendlyName";

@implementation ChannelManager

+ (instancetype)sharedManager {
  static ChannelManager *sharedMyManager = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedMyManager = [[self alloc] init];
  });
  return sharedMyManager;
}

- (instancetype)init {
  self.channels = [[NSMutableOrderedSet alloc] init];
  return self;
}

#pragma mark General channel

- (void)joinGeneralChatRoomWithCompletion:(SucceedHandler)completion {
  [self.channelsList channelWithSidOrUniqueName:TWCDefaultChannelUniqueName completion:^(TCHResult *result, TCHChannel *channel) {
    if ([result isSuccessful]) {
      self.generalChannel = channel;
    }

    if (self.generalChannel) {
      [self joinGeneralChatRoomWithUniqueName:nil completion:completion];
    }
    else {
      [self createGeneralChatRoomWithCompletion:^(BOOL succeeded) {
        if (succeeded) {
          [self joinGeneralChatRoomWithUniqueName:TWCDefaultChannelUniqueName completion:completion];
          return;
        }
        if (completion) completion(NO);
      }];
    };
  }];
}

- (void)joinGeneralChatRoomWithUniqueName:(NSString *)uniqueName completion:(SucceedHandler)completion {
  [self.generalChannel joinWithCompletion:^(TCHResult *result) {
    if ([result isSuccessful]) {
      if (uniqueName) {
        [self setGeneralChatRoomUniqueNameWithCompletion:completion];
        return;
      }
    }
    if (completion) completion([result isSuccessful]);
  }];
}

- (void)createGeneralChatRoomWithCompletion:(SucceedHandler)completion {
  NSDictionary *options = [
                           NSDictionary
                           dictionaryWithObjectsAndKeys:TWCDefaultChannelName,
                           TCHChannelOptionFriendlyName,
                           TCHChannelTypePublic,
                           TCHChannelOptionType,
                           nil
                           ];

  [self.channelsList createChannelWithOptions:options
    completion:^(TCHResult *result, TCHChannel *channel) {
      if ([result isSuccessful]) {
        self.generalChannel = channel;
      }
      if (completion) completion([result isSuccessful]);
    }];
}

- (void)setGeneralChatRoomUniqueNameWithCompletion:(SucceedHandler)completion {
  [self.generalChannel setUniqueName:TWCDefaultChannelUniqueName
                          completion:^(TCHResult *result) {
    if (completion) completion([result isSuccessful]);
  }];
}

#pragma mark Populate channels

- (void)populateChannels {
  self.channels = [[NSMutableOrderedSet alloc] init];
    [self.channelsList userChannelDescriptorsWithCompletion:^(TCHResult * _Nonnull result, TCHChannelDescriptorPaginator * _Nullable channelPaginator) {
    [self.channels addObjectsFromArray:[channelPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];

  [self.channelsList publicChannelDescriptorsWithCompletion:^(TCHResult *result,
                                                  TCHChannelDescriptorPaginator *channelDescPaginator) {
    [self.channels addObjectsFromArray: [channelDescPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];
}

- (void)sortAndDedupeChannels {
  NSMutableDictionary *channelsDict = [[NSMutableDictionary alloc] init];

  for(TCHChannel *channel in self.channels) {
    if (![channelsDict objectForKey: channel.sid] ||
        ![[channelsDict objectForKey: channel.sid] isKindOfClass: [NSNull class]]) {
      [channelsDict setObject:channel forKey:channel.sid];
    }
  }

  NSMutableOrderedSet *dedupedChannels = [NSMutableOrderedSet
                                          orderedSetWithArray:[channelsDict allValues]];

  SEL sortSelector = @selector(localizedCaseInsensitiveCompare:);

  NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:TWCFriendlyNameKey
                                                             ascending:YES
                                                              selector:sortSelector];

  [dedupedChannels sortUsingDescriptors:@[descriptor]];

  self.channels = dedupedChannels;
}

# pragma mark Create channel

- (void)createChannelWithName:(NSString *)name completion:(ChannelHandler)completion {
  if ([name isEqualToString:TWCDefaultChannelName]) {
    if (completion) completion(NO, nil);
    return;
  }

  NSDictionary *options = [
                           NSDictionary
                           dictionaryWithObjectsAndKeys:name,
                           TCHChannelOptionFriendlyName,
                           TCHChannelTypePublic,
                           TCHChannelOptionType,
                           nil
                           ];
  [self.channelsList
    createChannelWithOptions:options
    completion:^(TCHResult *result, TCHChannel *channel) {
      [self.channels addObject:channel];
      [self sortAndDedupeChannels];
      if (completion) completion([result isSuccessful], channel);
    }];
}

# pragma mark TwilioChatClientDelegate

- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel{
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.channels addObject:channel];
    [self sortAndDedupeChannels];
    [self.delegate chatClient:client channelAdded:channel];
  });
}

- (void)chatClient:(TwilioChatClient *)client channel:(nonnull TCHChannel *)channel updated:(TCHChannelUpdate)updated {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate chatClient:client channel:channel updated:updated];
  });
}

- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    [[ChannelManager sharedManager].channels removeObject:channel];
    [self.delegate chatClient:client channelDeleted:channel];
  });
}

- (void)chatClient:(TwilioChatClient *)client synchronizationStatusUpdated:(TCHClientSynchronizationStatus)status {

}

@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!

## Delete a Channel

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), call the channel's method `destroyWithCompletion`.

```objective-c title="Delete a Channel" description="twiliochat/MenuViewController.m"
// !mark(83,84,85,86,87,88,89,90,91,92,93,94,95)
#import <SWRevealViewController/SWRevealViewController.h>
#import "MenuViewController.h"
#import "MenuTableCell.h"
#import "InputDialogController.h"
#import "MainChatViewController.h"
#import "MessagingManager.h"
#import "AlertDialogController.h"
#import "ChannelManager.h"
#import "SessionManager.h"

@interface MenuViewController ()
@property (weak, nonatomic) IBOutlet UILabel *usernameLabel;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) UIRefreshControl *refreshControl;

@property (strong, nonatomic) TCHChannel *recentlyAddedChannel;
@end

static NSString * const TWCOpenChannelSegue = @"OpenChat";
static NSInteger const TWCRefreshControlXOffset = 120;


@implementation MenuViewController

#pragma mark Initialization

- (void)viewDidLoad {
  [super viewDidLoad];

  UIImageView *bgImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"home-bg"]];
  bgImage.frame = self.tableView.frame;
  self.tableView.backgroundView = bgImage;

  self.usernameLabel.text = [SessionManager getUsername];

  self.refreshControl = [[UIRefreshControl alloc] init];
  [self.tableView addSubview:self.refreshControl];
  [self.refreshControl addTarget:self
                          action:@selector(refreshChannels)
                forControlEvents:UIControlEventValueChanged];
  self.refreshControl.tintColor = [UIColor whiteColor];

  CGRect frame = self.refreshControl.frame;
  frame.origin.x = CGRectGetMinX(frame) - TWCRefreshControlXOffset;
  self.refreshControl.frame = frame;

  [ChannelManager sharedManager].delegate = self;
  [self reloadChannelList];
}

#pragma mark - Table view data source

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  if (![ChannelManager sharedManager].channels) {
    return 1;
  }

  return [ChannelManager sharedManager].channels.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  UITableViewCell *cell = nil;

  if (![ChannelManager sharedManager].channels) {
    cell = [self loadingCellForTableView:tableView];
  }
  else {
    cell = [self channelCellForTableView:tableView atIndexPath:indexPath];
  }
  [cell layoutIfNeeded];

  return cell;
}

#pragma mark - Table view delegate

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
  TCHChannel *channel = [[ChannelManager sharedManager].channels objectAtIndex:indexPath.row];
  return channel != [ChannelManager sharedManager].generalChannel;
}


- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
  if (editingStyle == UITableViewCellEditingStyleDelete) {
    TCHChannel *channel = [[ChannelManager sharedManager].channels objectAtIndex:indexPath.row];
    [channel destroyWithCompletion:^(TCHResult *result) {
      if ([result isSuccessful]) {
        [tableView reloadData];
      }
      else {
        [AlertDialogController showAlertWithMessage:@"You can not delete this channel" title:nil presenter:self];
      }
    }];
  }
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  [self performSegueWithIdentifier:TWCOpenChannelSegue sender:indexPath];
}

#pragma mark - Internal methods

- (UITableViewCell *)loadingCellForTableView:(UITableView *)tableView {
  return [tableView dequeueReusableCellWithIdentifier:@"loadingCell"];
}

- (UITableViewCell *)channelCellForTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
  MenuTableCell *menuCell = (MenuTableCell *)[tableView dequeueReusableCellWithIdentifier:@"channelCell" forIndexPath:indexPath];

  TCHChannel *channel = [[ChannelManager sharedManager].channels objectAtIndex:indexPath.row];
  NSString *friendlyName = channel.friendlyName;
  if (channel.friendlyName.length == 0) {
    friendlyName = @"(no friendly name)";
  }
  menuCell.channelName = friendlyName;

  return menuCell;
}

- (void)reloadChannelList {
  [self.tableView reloadData];
  [self.refreshControl endRefreshing];
}

- (void)refreshChannels {
  [self.refreshControl beginRefreshing];
  [self reloadChannelList];
}

- (void)deselectSelectedChannel {
  NSIndexPath *selectedRow = [self.tableView indexPathForSelectedRow];

  if (selectedRow) {
    [self.tableView deselectRowAtIndexPath:selectedRow animated:YES];
  }
}

#pragma mark - Channel

- (void)createNewChannelDialog {
  [InputDialogController showWithTitle:@"New Channel"
                               message:@"Enter a name for this channel."
                           placeholder:@"Name"
                             presenter:self handler:^(NSString *text) {
                               [[ChannelManager sharedManager] createChannelWithName:text completion:^(BOOL success, TCHChannel *channel) {
                                 if (success) {
                                   [self refreshChannels];
                                 }
                               }];
                             }];
}

#pragma mark - TwilioChatClientDelegate delegate

- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel {
  [self.tableView reloadData];
}

- (void)chatClient:(TwilioChatClient *)client channelChanged:(TCHChannel *)channel {
  [self.tableView reloadData];
}

- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
  [self.tableView reloadData];
}

#pragma mark - Logout

- (void)promtpLogout {
  UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
    message:@"You are about to Logout." preferredStyle:UIAlertControllerStyleAlert];

  UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel"
    style:UIAlertActionStyleCancel handler:nil];

  UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"Confirm"
    style:UIAlertActionStyleDefault
    handler:^(UIAlertAction *action) {
      [self logOut];
    }];

  [alert addAction:cancelAction];
  [alert addAction:confirmAction];
  [self presentViewController:alert animated:YES completion:nil];
}

- (void)logOut {
  [[MessagingManager sharedManager] logout];
  [[MessagingManager sharedManager] presentRootViewController];
}

#pragma mark Actions

- (IBAction)logoutButtonTouched:(UIButton *)sender {
  [self promtpLogout];
}

- (IBAction)newChannelButtonTouched:(UIButton *)sender {
  [self createNewChannelDialog];
}

#pragma mark - Navigation

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  if ([segue.identifier isEqualToString:TWCOpenChannelSegue]) {
    NSIndexPath *indexPath = (NSIndexPath *)sender;

    TCHChannel *channel = [[ChannelManager sharedManager].channels objectAtIndex:indexPath.row];
    UINavigationController *navigationController = [segue destinationViewController];
    MainChatViewController *chatViewController = (MainChatViewController *)[navigationController visibleViewController];
    chatViewController.channel = channel;
  }
}

#pragma mark Style

- (UIStatusBarStyle)preferredStatusBarStyle {
  return UIStatusBarStyleLightContent;
}


@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.

## Where to Next?

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

**[Notifications Quickstart](https://github.com/TwilioDevEd/notifications-quickstart-swift)**

Twilio Notifications for iOS Quickstart using Swift

### Did this help?

Thanks for checking out this tutorial! If you have any feedback to share with us, we'd love to hear it. Tweet [@twilio](http://twitter.com/twilio) to let us know what you think.
