Ready to implement a chat application using Twilio Programmable Chat Client? Here is how it works at a high level:
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.
#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
Now it's time to synchronize your Twilio 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
.
#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
We've initialized the Programmable Chat Client, now lets get a list of channels.
Our ChannelManager
class takes care of everything related to channels. In the previous step, we waited for the client to synchronize channel information, and assigned an instance of TCHChannelList to our ChannelManager
. Now we must get an actual array of channels using the userChannelsWithCompletion
and publicChannelsWithCompletion
methods.
#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
Let's see how we can listen to events from the chat client so we can update our app's state.
The Chat Client will trigger events such as channelAdded
or channelDeleted
on our application. Given the creation or deletion of a channel, we'll reload the channel list in the reveal controller. If a channel is deleted and we were currently joined to that channel, the application will automatically join the general channel.
ChannelManager
is a TwilioChatClientDelegate
. In this class we implement the delegate methods, but we also allow MenuViewController
class to be a delegate of ChannelManager, so it can listen to client events too.
#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
Next, we need a default channel.
This application will try to join a channel called "General Channel" when it starts. If the channel doesn't exist, it'll create one with that name. The scope of this example application will show you how to work only with public channels, but the Chat client allows you to create private channels and handle invitations.
Once you have joined a channel, you can register a class as the TCHChannelDelegate
so you can start listening to events such as messageAdded
or memberJoined
. We'll show you how to do this in the next step.
#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
Now let's listen for some channel events.
We registered MainChatViewController
as the TCHChannelDelegate
, and here we implemented the following methods that listen to channel events:
channelDeleted
: When someone deletes a channel.memberJoined
: When someone joins the channel.memberLeft
: When someone leaves the channel.messageAdded
: When someone sends a message to the channel you are connected to.synchronizationStatusChanged
: When channel synchronization status changes.As you may have noticed, each one of these methods include useful objects as parameters. One example is the actual message that was added to the channel.
#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
We've actually got a real chat app going here, but let's make it more interesting with multiple channels.
The application uses SWRevealViewController to show a sidebar that contains a list of the channels created for that Twilio account.
When you tap on the name of a channel from the sidebar, that channel is set on the MainChatViewController
. The setChannel
method takes care of joining to the selected channel and loading the messages.
#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
If we can join other channels, we'll need some way for a super user to create new channels (and delete old ones).
We use an input dialog so the user can type the name of the new channel. The only restriction here is that the user can't create a channel called "General Channel". Other than that, creating a channel is as simple as calling createChannelWithOptions
and passing a dictionary with the new channel information.
#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
Cool, we now know how to create a channel, let's say that we created a lot of channels by mistake. In that case, it would be useful to be able to delete those unnecessary channels. That's our next step!
Deleting a channel is easier than creating one. We'll use the UITableView
ability to delete a cell. Once you have figured out what channel is meant to be deleted (from the selected cell index path), deleting it is as simple as calling the channel's method destroyWithCompletion
.
#import <SWRevealViewController/SWRevealViewController.h>
#import "MenuViewController.h"
#import "MenuTableCell.h"
#import "InputDialogController.h"
#import "MainChatViewController.h"
#import "MessagingManager.h"
#import "AlertDialogController.h"
#import "ChannelManager.h"
#import "SessionManager.h"
@interface MenuViewController ()
@property (weak, nonatomic) IBOutlet UILabel *usernameLabel;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) UIRefreshControl *refreshControl;
@property (strong, nonatomic) TCHChannel *recentlyAddedChannel;
@end
static NSString * const TWCOpenChannelSegue = @"OpenChat";
static NSInteger const TWCRefreshControlXOffset = 120;
@implementation MenuViewController
#pragma mark Initialization
- (void)viewDidLoad {
[super viewDidLoad];
UIImageView *bgImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"home-bg"]];
bgImage.frame = self.tableView.frame;
self.tableView.backgroundView = bgImage;
self.usernameLabel.text = [SessionManager getUsername];
self.refreshControl = [[UIRefreshControl alloc] init];
[self.tableView addSubview:self.refreshControl];
[self.refreshControl addTarget:self
action:@selector(refreshChannels)
forControlEvents:UIControlEventValueChanged];
self.refreshControl.tintColor = [UIColor whiteColor];
CGRect frame = self.refreshControl.frame;
frame.origin.x = CGRectGetMinX(frame) - TWCRefreshControlXOffset;
self.refreshControl.frame = frame;
[ChannelManager sharedManager].delegate = self;
[self reloadChannelList];
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (![ChannelManager sharedManager].channels) {
return 1;
}
return [ChannelManager sharedManager].channels.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
if (![ChannelManager sharedManager].channels) {
cell = [self loadingCellForTableView:tableView];
}
else {
cell = [self channelCellForTableView:tableView atIndexPath:indexPath];
}
[cell layoutIfNeeded];
return cell;
}
#pragma mark - Table view delegate
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
TCHChannel *channel = [[ChannelManager sharedManager].channels objectAtIndex:indexPath.row];
return channel != [ChannelManager sharedManager].generalChannel;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
TCHChannel *channel = [[ChannelManager sharedManager].channels objectAtIndex:indexPath.row];
[channel destroyWithCompletion:^(TCHResult *result) {
if ([result isSuccessful]) {
[tableView reloadData];
}
else {
[AlertDialogController showAlertWithMessage:@"You can not delete this channel" title:nil presenter:self];
}
}];
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self performSegueWithIdentifier:TWCOpenChannelSegue sender:indexPath];
}
#pragma mark - Internal methods
- (UITableViewCell *)loadingCellForTableView:(UITableView *)tableView {
return [tableView dequeueReusableCellWithIdentifier:@"loadingCell"];
}
- (UITableViewCell *)channelCellForTableView:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath {
MenuTableCell *menuCell = (MenuTableCell *)[tableView dequeueReusableCellWithIdentifier:@"channelCell" forIndexPath:indexPath];
TCHChannel *channel = [[ChannelManager sharedManager].channels objectAtIndex:indexPath.row];
NSString *friendlyName = channel.friendlyName;
if (channel.friendlyName.length == 0) {
friendlyName = @"(no friendly name)";
}
menuCell.channelName = friendlyName;
return menuCell;
}
- (void)reloadChannelList {
[self.tableView reloadData];
[self.refreshControl endRefreshing];
}
- (void)refreshChannels {
[self.refreshControl beginRefreshing];
[self reloadChannelList];
}
- (void)deselectSelectedChannel {
NSIndexPath *selectedRow = [self.tableView indexPathForSelectedRow];
if (selectedRow) {
[self.tableView deselectRowAtIndexPath:selectedRow animated:YES];
}
}
#pragma mark - Channel
- (void)createNewChannelDialog {
[InputDialogController showWithTitle:@"New Channel"
message:@"Enter a name for this channel."
placeholder:@"Name"
presenter:self handler:^(NSString *text) {
[[ChannelManager sharedManager] createChannelWithName:text completion:^(BOOL success, TCHChannel *channel) {
if (success) {
[self refreshChannels];
}
}];
}];
}
#pragma mark - TwilioChatClientDelegate delegate
- (void)chatClient:(TwilioChatClient *)client channelAdded:(TCHChannel *)channel {
[self.tableView reloadData];
}
- (void)chatClient:(TwilioChatClient *)client channelChanged:(TCHChannel *)channel {
[self.tableView reloadData];
}
- (void)chatClient:(TwilioChatClient *)client channelDeleted:(TCHChannel *)channel {
[self.tableView reloadData];
}
#pragma mark - Logout
- (void)promtpLogout {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
message:@"You are about to Logout." preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleCancel handler:nil];
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"Confirm"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[self logOut];
}];
[alert addAction:cancelAction];
[alert addAction:confirmAction];
[self presentViewController:alert animated:YES completion:nil];
}
- (void)logOut {
[[MessagingManager sharedManager] logout];
[[MessagingManager sharedManager] presentRootViewController];
}
#pragma mark Actions
- (IBAction)logoutButtonTouched:(UIButton *)sender {
[self promtpLogout];
}
- (IBAction)newChannelButtonTouched:(UIButton *)sender {
[self createNewChannelDialog];
}
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:TWCOpenChannelSegue]) {
NSIndexPath *indexPath = (NSIndexPath *)sender;
TCHChannel *channel = [[ChannelManager sharedManager].channels objectAtIndex:indexPath.row];
UINavigationController *navigationController = [segue destinationViewController];
MainChatViewController *chatViewController = (MainChatViewController *)[navigationController visibleViewController];
chatViewController.channel = channel;
}
}
#pragma mark Style
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
@end
That's it! We've built an iOS application with Objective-C. Now you are more than prepared to set up your own chat application.
If you are am iOS developer working with Twilio, you might want to check out these other projects:
Twilio Client for iOS Quickstart using Objective-C
Twilio Notifications for iOS Quickstart using Objective-C
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.
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.
#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