Chat with iOS and Objective-C

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

  1. Twilio Programmable Chat is the core product we'll be using to handle all the chat functionality.
  2. We use a server side app to generate user access tokens which contains all your Twilio account information. The Chat Client uses this token to connect with the API
  3. Twilio Access Manager is the part of the SDK than handles access tokens and even refreshes them upon token expiration.

Properati built a web and mobile messaging app to help real estate buyers and sellers connect in real time. Learn more here.

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:

We use AFNetworking to make a request to our server and get the access token.

Loading Code Samples...
Language
#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) {
        [self presentViewControllerByName:TWCMainViewControllerName];
      }
    }];
  }

}

- (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 {
  self.client = [TwilioChatClient chatClientWithToken:token properties:nil delegate:self];
  [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
  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 TwilioAccessManagerDelegate

- (void)accessManagerTokenExpired:(TwilioAccessManager *)accessManager {
  [self requestTokenWithCompletion:^(BOOL succeeded, NSString *token) {
    if (succeeded) {
      [accessManager updateToken:token];
    }
    else {
      NSLog(@"Error while trying to get new access token");
    }
  }];
}

- (void)accessManager:(TwilioAccessManager *)accessManager error:(NSError *)error {
  NSLog(@"Access manager error: %@", [error localizedDescription]);
}

#pragma mark Internal helpers

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

#pragma mark TwilioChatClientDelegate

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

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

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

- (void)chatClient:(TwilioChatClient *)client synchronizationStatusChanged:(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 presentRootViewController];
    }];
  }
  [self.delegate chatClient:client synchronizationStatusChanged:status];
}

@end
twiliochat/MessagingManager.m
Fetch Access Token

twiliochat/MessagingManager.m

Now it's time to synchronize your Twilio Client.

Synchronize the Chat Client

In the previous step we initialized the Chat client with an Access Manager and set the client's delegate to the shared instance of our MessagingManager.

The synchronizationStatusChanged 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.

Loading Code Samples...
Language
#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) {
        [self presentViewControllerByName:TWCMainViewControllerName];
      }
    }];
  }

}

- (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 {
  self.client = [TwilioChatClient chatClientWithToken:token properties:nil delegate:self];
  [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
  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 TwilioAccessManagerDelegate

- (void)accessManagerTokenExpired:(TwilioAccessManager *)accessManager {
  [self requestTokenWithCompletion:^(BOOL succeeded, NSString *token) {
    if (succeeded) {
      [accessManager updateToken:token];
    }
    else {
      NSLog(@"Error while trying to get new access token");
    }
  }];
}

- (void)accessManager:(TwilioAccessManager *)accessManager error:(NSError *)error {
  NSLog(@"Access manager error: %@", [error localizedDescription]);
}

#pragma mark Internal helpers

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

#pragma mark TwilioChatClientDelegate

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

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

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

- (void)chatClient:(TwilioChatClient *)client synchronizationStatusChanged:(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 presentRootViewController];
    }];
  }
  [self.delegate chatClient:client synchronizationStatusChanged:status];
}

@end
twiliochat/MessagingManager.m
Synchronize the Chat Client

twiliochat/MessagingManager.m

We've initialized the Programmable Chat Client, now lets 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 to our ChannelManager. Now we must get an actual array of channels using the userChannelsWithCompletion and publicChannelsWithCompletion methods.

Loading Code Samples...
Language
#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 userChannelsWithCompletion:^(TCHResult *result,
                                                  TCHChannelPaginator *channelPaginator) {
    [self.channels addObjectsFromArray:[channelPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];

  [self.channelsList publicChannelsWithCompletion:^(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 channelChanged:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate chatClient:client channelChanged:channel];
  });
}

- (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 synchronizationStatusChanged:(TCHClientSynchronizationStatus)status {
}

@end
twiliochat/ChannelManager.m
Get Channel List

twiliochat/ChannelManager.m

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

Loading Code Samples...
Language
#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 userChannelsWithCompletion:^(TCHResult *result,
                                                  TCHChannelPaginator *channelPaginator) {
    [self.channels addObjectsFromArray:[channelPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];

  [self.channelsList publicChannelsWithCompletion:^(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 channelChanged:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate chatClient:client channelChanged:channel];
  });
}

- (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 synchronizationStatusChanged:(TCHClientSynchronizationStatus)status {
}

@end
twiliochat/ChannelManager.m
Listen for Client Events

twiliochat/ChannelManager.m

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.

Loading Code Samples...
Language
#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 userChannelsWithCompletion:^(TCHResult *result,
                                                  TCHChannelPaginator *channelPaginator) {
    [self.channels addObjectsFromArray:[channelPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];

  [self.channelsList publicChannelsWithCompletion:^(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 channelChanged:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate chatClient:client channelChanged:channel];
  });
}

- (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 synchronizationStatusChanged:(TCHClientSynchronizationStatus)status {
}

@end
twiliochat/ChannelManager.m
Join or Create a General Channel

twiliochat/ChannelManager.m

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 include useful objects as parameters. One example is the actual message that was added to the channel.

Loading Code Samples...
Language
#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) {
    self.channel = [ChannelManager sharedManager].generalChannel;
  }
}

- (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");
    }];
  }
  if (self.channel.synchronizationStatus != TCHChannelSynchronizationStatusAll) {
    [self.channel synchronizeWithCompletion:^(TCHResult *result) {
      if ([result isSuccessful]) {
        NSLog(@"%@", @"Synchronization started. Delegate method will load messages");
      }
    }];
  }
  else {
    [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 getStatuCellForTableView: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 *)getStatuCellForTableView:(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.userInfo.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 {
  TCHMessage *message = [self.channel.messages createMessageWithBody:inputMessage];
  [self.channel.messages sendMessage:message 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
twiliochat/MainChatViewController.m
Listen to Channel Events

twiliochat/MainChatViewController.m

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

Loading Code Samples...
Language
#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) {
    self.channel = [ChannelManager sharedManager].generalChannel;
  }
}

- (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");
    }];
  }
  if (self.channel.synchronizationStatus != TCHChannelSynchronizationStatusAll) {
    [self.channel synchronizeWithCompletion:^(TCHResult *result) {
      if ([result isSuccessful]) {
        NSLog(@"%@", @"Synchronization started. Delegate method will load messages");
      }
    }];
  }
  else {
    [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 getStatuCellForTableView: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 *)getStatuCellForTableView:(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.userInfo.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 {
  TCHMessage *message = [self.channel.messages createMessageWithBody:inputMessage];
  [self.channel.messages sendMessage:message 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
twiliochat/MainChatViewController.m
Join Other Channels

twiliochat/MainChatViewController.m

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

Loading Code Samples...
Language
#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 userChannelsWithCompletion:^(TCHResult *result,
                                                  TCHChannelPaginator *channelPaginator) {
    [self.channels addObjectsFromArray:[channelPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];

  [self.channelsList publicChannelsWithCompletion:^(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 channelChanged:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate chatClient:client channelChanged:channel];
  });
}

- (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 synchronizationStatusChanged:(TCHClientSynchronizationStatus)status {
}

@end
twiliochat/ChannelManager.m
Create a Channel

twiliochat/ChannelManager.m

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), deleting it is as simple as calling the channel's method destroyWithCompletion.

Loading Code Samples...
Language
#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
twiliochat/MenuViewController.m
Delete a Channel

twiliochat/MenuViewController.m

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 am iOS developer working with Twilio, you might want to check out these other projects:

Client Quickstart

Twilio Client for iOS Quickstart using Objective-C

Notifications Quickstart

Twilio Notifications for iOS Quickstart using Objective-C

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 to let us know what you think.

Mario Celi
David Prothero
Andrew Baker
Agustin Camino
Jose Oliveros

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
#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) {
        [self presentViewControllerByName:TWCMainViewControllerName];
      }
    }];
  }

}

- (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 {
  self.client = [TwilioChatClient chatClientWithToken:token properties:nil delegate:self];
  [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
  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 TwilioAccessManagerDelegate

- (void)accessManagerTokenExpired:(TwilioAccessManager *)accessManager {
  [self requestTokenWithCompletion:^(BOOL succeeded, NSString *token) {
    if (succeeded) {
      [accessManager updateToken:token];
    }
    else {
      NSLog(@"Error while trying to get new access token");
    }
  }];
}

- (void)accessManager:(TwilioAccessManager *)accessManager error:(NSError *)error {
  NSLog(@"Access manager error: %@", [error localizedDescription]);
}

#pragma mark Internal helpers

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

#pragma mark TwilioChatClientDelegate

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

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

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

- (void)chatClient:(TwilioChatClient *)client synchronizationStatusChanged:(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 presentRootViewController];
    }];
  }
  [self.delegate chatClient:client synchronizationStatusChanged:status];
}

@end
#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) {
        [self presentViewControllerByName:TWCMainViewControllerName];
      }
    }];
  }

}

- (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 {
  self.client = [TwilioChatClient chatClientWithToken:token properties:nil delegate:self];
  [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
  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 TwilioAccessManagerDelegate

- (void)accessManagerTokenExpired:(TwilioAccessManager *)accessManager {
  [self requestTokenWithCompletion:^(BOOL succeeded, NSString *token) {
    if (succeeded) {
      [accessManager updateToken:token];
    }
    else {
      NSLog(@"Error while trying to get new access token");
    }
  }];
}

- (void)accessManager:(TwilioAccessManager *)accessManager error:(NSError *)error {
  NSLog(@"Access manager error: %@", [error localizedDescription]);
}

#pragma mark Internal helpers

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

#pragma mark TwilioChatClientDelegate

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

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

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

- (void)chatClient:(TwilioChatClient *)client synchronizationStatusChanged:(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 presentRootViewController];
    }];
  }
  [self.delegate chatClient:client synchronizationStatusChanged:status];
}

@end
#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 userChannelsWithCompletion:^(TCHResult *result,
                                                  TCHChannelPaginator *channelPaginator) {
    [self.channels addObjectsFromArray:[channelPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];

  [self.channelsList publicChannelsWithCompletion:^(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 channelChanged:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate chatClient:client channelChanged:channel];
  });
}

- (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 synchronizationStatusChanged:(TCHClientSynchronizationStatus)status {
}

@end
#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 userChannelsWithCompletion:^(TCHResult *result,
                                                  TCHChannelPaginator *channelPaginator) {
    [self.channels addObjectsFromArray:[channelPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];

  [self.channelsList publicChannelsWithCompletion:^(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 channelChanged:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate chatClient:client channelChanged:channel];
  });
}

- (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 synchronizationStatusChanged:(TCHClientSynchronizationStatus)status {
}

@end
#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 userChannelsWithCompletion:^(TCHResult *result,
                                                  TCHChannelPaginator *channelPaginator) {
    [self.channels addObjectsFromArray:[channelPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];

  [self.channelsList publicChannelsWithCompletion:^(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 channelChanged:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate chatClient:client channelChanged:channel];
  });
}

- (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 synchronizationStatusChanged:(TCHClientSynchronizationStatus)status {
}

@end
#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) {
    self.channel = [ChannelManager sharedManager].generalChannel;
  }
}

- (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");
    }];
  }
  if (self.channel.synchronizationStatus != TCHChannelSynchronizationStatusAll) {
    [self.channel synchronizeWithCompletion:^(TCHResult *result) {
      if ([result isSuccessful]) {
        NSLog(@"%@", @"Synchronization started. Delegate method will load messages");
      }
    }];
  }
  else {
    [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 getStatuCellForTableView: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 *)getStatuCellForTableView:(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.userInfo.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 {
  TCHMessage *message = [self.channel.messages createMessageWithBody:inputMessage];
  [self.channel.messages sendMessage:message 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
#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) {
    self.channel = [ChannelManager sharedManager].generalChannel;
  }
}

- (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");
    }];
  }
  if (self.channel.synchronizationStatus != TCHChannelSynchronizationStatusAll) {
    [self.channel synchronizeWithCompletion:^(TCHResult *result) {
      if ([result isSuccessful]) {
        NSLog(@"%@", @"Synchronization started. Delegate method will load messages");
      }
    }];
  }
  else {
    [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 getStatuCellForTableView: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 *)getStatuCellForTableView:(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.userInfo.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 {
  TCHMessage *message = [self.channel.messages createMessageWithBody:inputMessage];
  [self.channel.messages sendMessage:message 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
#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 userChannelsWithCompletion:^(TCHResult *result,
                                                  TCHChannelPaginator *channelPaginator) {
    [self.channels addObjectsFromArray:[channelPaginator items]];
    [self sortAndDedupeChannels];
    if (self.delegate) {
      [self.delegate reloadChannelList];
    }
  }];

  [self.channelsList publicChannelsWithCompletion:^(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 channelChanged:(TCHChannel *)channel {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.delegate chatClient:client channelChanged:channel];
  });
}

- (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 synchronizationStatusChanged:(TCHClientSynchronizationStatus)status {
}

@end
#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