Programmable Chat has been deprecated and is no longer supported. Instead, we'll be focusing on the next generation of chat: Twilio Conversations. Find out more about the EOL process here.
If you're starting a new project, please visit the Conversations Docs to begin. If you've already built on Programmable Chat, please visit our Migration Guide to learn about how to switch.
As the Programmable Chat API is set to sunset in 2022, we will no longer maintain these chat tutorials.
Please see our Conversations API QuickStart to start building robust virtual spaces for conversation.
Ready to implement a chat application using Twilio Chat Client? Here is how it works at a high level:
For your convenience, we consolidated the source code for this tutorial in a single GitHub repository. Feel free to clone it and tweak as required.
The only thing you need to create a client is an access token. This token holds information about your Twilio account and Programmable 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:
You will need to set up your access token URL in the Keys.plist file in the resources folder. The default is http://localhost:8000/token - you may need to change this. For instance, if you set up the Node.js version of the chat server (listed above) - this URL would be http://localhost:3000/token
twiliochat/MessagingManager.swift
1import UIKit23class MessagingManager: NSObject {45static let _sharedManager = MessagingManager()67var client:TwilioChatClient?8var delegate:ChannelManager?9var connected = false1011var userIdentity:String {12return SessionManager.getUsername()13}1415var hasIdentity: Bool {16return SessionManager.isLoggedIn()17}1819override init() {20super.init()21delegate = ChannelManager.sharedManager22}2324class func sharedManager() -> MessagingManager {25return _sharedManager26}2728func presentRootViewController() {29if (!hasIdentity) {30presentViewControllerByName(viewController: "LoginViewController")31return32}3334if (!connected) {35connectClientWithCompletion { success, error in36print("Delegate method will load views when sync is complete")37if (!success || error != nil) {38DispatchQueue.main.async {39self.presentViewControllerByName(viewController: "LoginViewController")40}41}42}43return44}4546presentViewControllerByName(viewController: "RevealViewController")47}4849func presentViewControllerByName(viewController: String) {50presentViewController(controller: storyBoardWithName(name: "Main").instantiateViewController(withIdentifier: viewController))51}5253func presentLaunchScreen() {54presentViewController(controller: storyBoardWithName(name: "LaunchScreen").instantiateInitialViewController()!)55}5657func presentViewController(controller: UIViewController) {58let window = UIApplication.shared.delegate!.window!!59window.rootViewController = controller60}6162func storyBoardWithName(name:String) -> UIStoryboard {63return UIStoryboard(name:name, bundle: Bundle.main)64}6566// MARK: User and session management6768func loginWithUsername(username: String,69completion: @escaping (Bool, NSError?) -> Void) {70SessionManager.loginWithUsername(username: username)71connectClientWithCompletion(completion: completion)72}7374func logout() {75SessionManager.logout()76DispatchQueue.global(qos: .userInitiated).async {77self.client?.shutdown()78self.client = nil79}80self.connected = false81}8283// MARK: Twilio client8485func loadGeneralChatRoomWithCompletion(completion:@escaping (Bool, NSError?) -> Void) {86ChannelManager.sharedManager.joinGeneralChatRoomWithCompletion { succeeded in87if succeeded {88completion(succeeded, nil)89}90else {91let error = self.errorWithDescription(description: "Could not join General channel", code: 300)92completion(succeeded, error)93}94}95}9697func connectClientWithCompletion(completion: @escaping (Bool, NSError?) -> Void) {98if (client != nil) {99logout()100}101102requestTokenWithCompletion { succeeded, token in103if let token = token, succeeded {104self.initializeClientWithToken(token: token)105completion(succeeded, nil)106}107else {108let error = self.errorWithDescription(description: "Could not get access token", code:301)109completion(succeeded, error)110}111}112}113114func initializeClientWithToken(token: String) {115DispatchQueue.main.async {116UIApplication.shared.isNetworkActivityIndicatorVisible = true117}118TwilioChatClient.chatClient(withToken: token, properties: nil, delegate: self) { [weak self] result, chatClient in119guard (result.isSuccessful()) else { return }120121UIApplication.shared.isNetworkActivityIndicatorVisible = true122self?.connected = true123self?.client = chatClient124}125}126127func requestTokenWithCompletion(completion:@escaping (Bool, String?) -> Void) {128if let device = UIDevice.current.identifierForVendor?.uuidString {129TokenRequestHandler.fetchToken(params: ["device": device, "identity":SessionManager.getUsername()]) {response,error in130var token: String?131token = response["token"] as? String132completion(token != nil, token)133}134}135}136137func errorWithDescription(description: String, code: Int) -> NSError {138let userInfo = [NSLocalizedDescriptionKey : description]139return NSError(domain: "app", code: code, userInfo: userInfo)140}141}142143// MARK: - TwilioChatClientDelegate144extension MessagingManager : TwilioChatClientDelegate {145func chatClient(_ client: TwilioChatClient, channelAdded channel: TCHChannel) {146self.delegate?.chatClient(client, channelAdded: channel)147}148149func chatClient(_ client: TwilioChatClient, channel: TCHChannel, updated: TCHChannelUpdate) {150self.delegate?.chatClient(client, channel: channel, updated: updated)151}152153func chatClient(_ client: TwilioChatClient, channelDeleted channel: TCHChannel) {154self.delegate?.chatClient(client, channelDeleted: channel)155}156157func chatClient(_ client: TwilioChatClient, synchronizationStatusUpdated status: TCHClientSynchronizationStatus) {158if status == TCHClientSynchronizationStatus.completed {159UIApplication.shared.isNetworkActivityIndicatorVisible = false160ChannelManager.sharedManager.channelsList = client.channelsList()161ChannelManager.sharedManager.populateChannelDescriptors()162loadGeneralChatRoomWithCompletion { success, error in163if success {164self.presentRootViewController()165}166}167}168self.delegate?.chatClient(client, synchronizationStatusUpdated: status)169}170171func chatClientTokenWillExpire(_ client: TwilioChatClient) {172requestTokenWithCompletion { succeeded, token in173if (succeeded) {174client.updateToken(token!)175}176else {177print("Error while trying to get new access token")178}179}180}181182func chatClientTokenExpired(_ client: TwilioChatClient) {183requestTokenWithCompletion { succeeded, token in184if (succeeded) {185client.updateToken(token!)186}187else {188print("Error while trying to get new access token")189}190}191}192}193
Now it's time to synchronize your Twilio client.
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 (next step). Otherwise, calling client.channelsList() will return nil
.
twiliochat/MessagingManager.swift
1import UIKit23class MessagingManager: NSObject {45static let _sharedManager = MessagingManager()67var client:TwilioChatClient?8var delegate:ChannelManager?9var connected = false1011var userIdentity:String {12return SessionManager.getUsername()13}1415var hasIdentity: Bool {16return SessionManager.isLoggedIn()17}1819override init() {20super.init()21delegate = ChannelManager.sharedManager22}2324class func sharedManager() -> MessagingManager {25return _sharedManager26}2728func presentRootViewController() {29if (!hasIdentity) {30presentViewControllerByName(viewController: "LoginViewController")31return32}3334if (!connected) {35connectClientWithCompletion { success, error in36print("Delegate method will load views when sync is complete")37if (!success || error != nil) {38DispatchQueue.main.async {39self.presentViewControllerByName(viewController: "LoginViewController")40}41}42}43return44}4546presentViewControllerByName(viewController: "RevealViewController")47}4849func presentViewControllerByName(viewController: String) {50presentViewController(controller: storyBoardWithName(name: "Main").instantiateViewController(withIdentifier: viewController))51}5253func presentLaunchScreen() {54presentViewController(controller: storyBoardWithName(name: "LaunchScreen").instantiateInitialViewController()!)55}5657func presentViewController(controller: UIViewController) {58let window = UIApplication.shared.delegate!.window!!59window.rootViewController = controller60}6162func storyBoardWithName(name:String) -> UIStoryboard {63return UIStoryboard(name:name, bundle: Bundle.main)64}6566// MARK: User and session management6768func loginWithUsername(username: String,69completion: @escaping (Bool, NSError?) -> Void) {70SessionManager.loginWithUsername(username: username)71connectClientWithCompletion(completion: completion)72}7374func logout() {75SessionManager.logout()76DispatchQueue.global(qos: .userInitiated).async {77self.client?.shutdown()78self.client = nil79}80self.connected = false81}8283// MARK: Twilio client8485func loadGeneralChatRoomWithCompletion(completion:@escaping (Bool, NSError?) -> Void) {86ChannelManager.sharedManager.joinGeneralChatRoomWithCompletion { succeeded in87if succeeded {88completion(succeeded, nil)89}90else {91let error = self.errorWithDescription(description: "Could not join General channel", code: 300)92completion(succeeded, error)93}94}95}9697func connectClientWithCompletion(completion: @escaping (Bool, NSError?) -> Void) {98if (client != nil) {99logout()100}101102requestTokenWithCompletion { succeeded, token in103if let token = token, succeeded {104self.initializeClientWithToken(token: token)105completion(succeeded, nil)106}107else {108let error = self.errorWithDescription(description: "Could not get access token", code:301)109completion(succeeded, error)110}111}112}113114func initializeClientWithToken(token: String) {115DispatchQueue.main.async {116UIApplication.shared.isNetworkActivityIndicatorVisible = true117}118TwilioChatClient.chatClient(withToken: token, properties: nil, delegate: self) { [weak self] result, chatClient in119guard (result.isSuccessful()) else { return }120121UIApplication.shared.isNetworkActivityIndicatorVisible = true122self?.connected = true123self?.client = chatClient124}125}126127func requestTokenWithCompletion(completion:@escaping (Bool, String?) -> Void) {128if let device = UIDevice.current.identifierForVendor?.uuidString {129TokenRequestHandler.fetchToken(params: ["device": device, "identity":SessionManager.getUsername()]) {response,error in130var token: String?131token = response["token"] as? String132completion(token != nil, token)133}134}135}136137func errorWithDescription(description: String, code: Int) -> NSError {138let userInfo = [NSLocalizedDescriptionKey : description]139return NSError(domain: "app", code: code, userInfo: userInfo)140}141}142143// MARK: - TwilioChatClientDelegate144extension MessagingManager : TwilioChatClientDelegate {145func chatClient(_ client: TwilioChatClient, channelAdded channel: TCHChannel) {146self.delegate?.chatClient(client, channelAdded: channel)147}148149func chatClient(_ client: TwilioChatClient, channel: TCHChannel, updated: TCHChannelUpdate) {150self.delegate?.chatClient(client, channel: channel, updated: updated)151}152153func chatClient(_ client: TwilioChatClient, channelDeleted channel: TCHChannel) {154self.delegate?.chatClient(client, channelDeleted: channel)155}156157func chatClient(_ client: TwilioChatClient, synchronizationStatusUpdated status: TCHClientSynchronizationStatus) {158if status == TCHClientSynchronizationStatus.completed {159UIApplication.shared.isNetworkActivityIndicatorVisible = false160ChannelManager.sharedManager.channelsList = client.channelsList()161ChannelManager.sharedManager.populateChannelDescriptors()162loadGeneralChatRoomWithCompletion { success, error in163if success {164self.presentRootViewController()165}166}167}168self.delegate?.chatClient(client, synchronizationStatusUpdated: status)169}170171func chatClientTokenWillExpire(_ client: TwilioChatClient) {172requestTokenWithCompletion { succeeded, token in173if (succeeded) {174client.updateToken(token!)175}176else {177print("Error while trying to get new access token")178}179}180}181182func chatClientTokenExpired(_ client: TwilioChatClient) {183requestTokenWithCompletion { succeeded, token in184if (succeeded) {185client.updateToken(token!)186}187else {188print("Error while trying to get new access token")189}190}191}192}193
We've initialized the Programmable Chat Client, now let's get a list of channels.
Our ChannelManager
class takes care of everything related to channels. In the previous step, we waited for the client to synchronize channel information, and assigned an instance of TCHChannels to our ChannelManager
.
Now we will get a list of light-weight channel descriptors to use for the list of channels in our application. We combine the channels the user has subscribed to (both public and private) with the list of publicly available channels. We do need to merge this list, and avoid adding duplicates. We also sort the channel list here alphabetically by the friendly name.
twiliochat/ChannelManager.swift
1import UIKit23protocol ChannelManagerDelegate {4func reloadChannelDescriptorList()5}67class ChannelManager: NSObject {8static let sharedManager = ChannelManager()910static let defaultChannelUniqueName = "general"11static let defaultChannelName = "General Channel"1213var delegate:ChannelManagerDelegate?1415var channelsList:TCHChannels?16var channelDescriptors:NSOrderedSet?17var generalChannel:TCHChannel!1819override init() {20super.init()21channelDescriptors = NSMutableOrderedSet()22}2324// MARK: - General channel2526func joinGeneralChatRoomWithCompletion(completion: @escaping (Bool) -> Void) {2728let uniqueName = ChannelManager.defaultChannelUniqueName29if let channelsList = self.channelsList {30channelsList.channel(withSidOrUniqueName: uniqueName) { result, channel in31self.generalChannel = channel3233if self.generalChannel != nil {34self.joinGeneralChatRoomWithUniqueName(name: nil, completion: completion)35} else {36self.createGeneralChatRoomWithCompletion { succeeded in37if (succeeded) {38self.joinGeneralChatRoomWithUniqueName(name: uniqueName, completion: completion)39return40}4142completion(false)43}44}45}46}47}4849func joinGeneralChatRoomWithUniqueName(name: String?, completion: @escaping (Bool) -> Void) {50generalChannel.join { result in51if ((result.isSuccessful()) && name != nil) {52self.setGeneralChatRoomUniqueNameWithCompletion(completion: completion)53return54}55completion((result.isSuccessful()))56}57}5859func createGeneralChatRoomWithCompletion(completion: @escaping (Bool) -> Void) {60let channelName = ChannelManager.defaultChannelName61let options = [62TCHChannelOptionFriendlyName: channelName,63TCHChannelOptionType: TCHChannelType.public.rawValue64] as [String : Any]65channelsList!.createChannel(options: options) { result, channel in66if (result.isSuccessful()) {67self.generalChannel = channel68}69completion((result.isSuccessful()))70}71}7273func setGeneralChatRoomUniqueNameWithCompletion(completion:@escaping (Bool) -> Void) {74generalChannel.setUniqueName(ChannelManager.defaultChannelUniqueName) { result in75completion((result.isSuccessful()))76}77}7879// MARK: - Populate channel Descriptors8081func populateChannelDescriptors() {8283channelsList?.userChannelDescriptors { result, paginator in84guard let paginator = paginator else {85return86}8788let newChannelDescriptors = NSMutableOrderedSet()89newChannelDescriptors.addObjects(from: paginator.items())90self.channelsList?.publicChannelDescriptors { result, paginator in91guard let paginator = paginator else {92return93}9495// de-dupe channel list96let channelIds = NSMutableSet()97for descriptor in newChannelDescriptors {98if let descriptor = descriptor as? TCHChannelDescriptor {99if let sid = descriptor.sid {100channelIds.add(sid)101}102}103}104for descriptor in paginator.items() {105if let sid = descriptor.sid {106if !channelIds.contains(sid) {107channelIds.add(sid)108newChannelDescriptors.add(descriptor)109}110}111}112113114// sort the descriptors115let sortSelector = #selector(NSString.localizedCaseInsensitiveCompare(_:))116let descriptor = NSSortDescriptor(key: "friendlyName", ascending: true, selector: sortSelector)117newChannelDescriptors.sort(using: [descriptor])118119self.channelDescriptors = newChannelDescriptors120121if let delegate = self.delegate {122delegate.reloadChannelDescriptorList()123}124}125}126}127128129// MARK: - Create channel130131func createChannelWithName(name: String, completion: @escaping (Bool, TCHChannel?) -> Void) {132if (name == ChannelManager.defaultChannelName) {133completion(false, nil)134return135}136137let channelOptions = [138TCHChannelOptionFriendlyName: name,139TCHChannelOptionType: TCHChannelType.public.rawValue140] as [String : Any]141UIApplication.shared.isNetworkActivityIndicatorVisible = true;142self.channelsList?.createChannel(options: channelOptions) { result, channel in143UIApplication.shared.isNetworkActivityIndicatorVisible = false144completion((result.isSuccessful()), channel)145}146}147}148149// MARK: - TwilioChatClientDelegate150extension ChannelManager : TwilioChatClientDelegate {151func chatClient(_ client: TwilioChatClient, channelAdded channel: TCHChannel) {152DispatchQueue.main.async {153self.populateChannelDescriptors()154}155}156157func chatClient(_ client: TwilioChatClient, channel: TCHChannel, updated: TCHChannelUpdate) {158DispatchQueue.main.async {159self.delegate?.reloadChannelDescriptorList()160}161}162163func chatClient(_ client: TwilioChatClient, channelDeleted channel: TCHChannel) {164DispatchQueue.main.async {165self.populateChannelDescriptors()166}167168}169170func chatClient(_ client: TwilioChatClient, synchronizationStatusUpdated status: TCHClientSynchronizationStatus) {171}172}173
Let's see how we can listen to events from the chat client so we can update our app's state.
The Programmable Chat Client will trigger events such as channelAdded
or channelDeleted
on our application. Given the creation or deletion of a channel, we'll reload the channel list in the reveal controller. If a channel is deleted and we were currently joined to that channel, the application will automatically join the general channel.
ChannelManager
is a TwilioChatClientDelegate
. In this class we implement the delegate methods, but we also allow MenuViewController
class to be a delegate of ChannelManager, so it can listen to client events too.
twiliochat/ChannelManager.swift
1import UIKit23protocol ChannelManagerDelegate {4func reloadChannelDescriptorList()5}67class ChannelManager: NSObject {8static let sharedManager = ChannelManager()910static let defaultChannelUniqueName = "general"11static let defaultChannelName = "General Channel"1213var delegate:ChannelManagerDelegate?1415var channelsList:TCHChannels?16var channelDescriptors:NSOrderedSet?17var generalChannel:TCHChannel!1819override init() {20super.init()21channelDescriptors = NSMutableOrderedSet()22}2324// MARK: - General channel2526func joinGeneralChatRoomWithCompletion(completion: @escaping (Bool) -> Void) {2728let uniqueName = ChannelManager.defaultChannelUniqueName29if let channelsList = self.channelsList {30channelsList.channel(withSidOrUniqueName: uniqueName) { result, channel in31self.generalChannel = channel3233if self.generalChannel != nil {34self.joinGeneralChatRoomWithUniqueName(name: nil, completion: completion)35} else {36self.createGeneralChatRoomWithCompletion { succeeded in37if (succeeded) {38self.joinGeneralChatRoomWithUniqueName(name: uniqueName, completion: completion)39return40}4142completion(false)43}44}45}46}47}4849func joinGeneralChatRoomWithUniqueName(name: String?, completion: @escaping (Bool) -> Void) {50generalChannel.join { result in51if ((result.isSuccessful()) && name != nil) {52self.setGeneralChatRoomUniqueNameWithCompletion(completion: completion)53return54}55completion((result.isSuccessful()))56}57}5859func createGeneralChatRoomWithCompletion(completion: @escaping (Bool) -> Void) {60let channelName = ChannelManager.defaultChannelName61let options = [62TCHChannelOptionFriendlyName: channelName,63TCHChannelOptionType: TCHChannelType.public.rawValue64] as [String : Any]65channelsList!.createChannel(options: options) { result, channel in66if (result.isSuccessful()) {67self.generalChannel = channel68}69completion((result.isSuccessful()))70}71}7273func setGeneralChatRoomUniqueNameWithCompletion(completion:@escaping (Bool) -> Void) {74generalChannel.setUniqueName(ChannelManager.defaultChannelUniqueName) { result in75completion((result.isSuccessful()))76}77}7879// MARK: - Populate channel Descriptors8081func populateChannelDescriptors() {8283channelsList?.userChannelDescriptors { result, paginator in84guard let paginator = paginator else {85return86}8788let newChannelDescriptors = NSMutableOrderedSet()89newChannelDescriptors.addObjects(from: paginator.items())90self.channelsList?.publicChannelDescriptors { result, paginator in91guard let paginator = paginator else {92return93}9495// de-dupe channel list96let channelIds = NSMutableSet()97for descriptor in newChannelDescriptors {98if let descriptor = descriptor as? TCHChannelDescriptor {99if let sid = descriptor.sid {100channelIds.add(sid)101}102}103}104for descriptor in paginator.items() {105if let sid = descriptor.sid {106if !channelIds.contains(sid) {107channelIds.add(sid)108newChannelDescriptors.add(descriptor)109}110}111}112113114// sort the descriptors115let sortSelector = #selector(NSString.localizedCaseInsensitiveCompare(_:))116let descriptor = NSSortDescriptor(key: "friendlyName", ascending: true, selector: sortSelector)117newChannelDescriptors.sort(using: [descriptor])118119self.channelDescriptors = newChannelDescriptors120121if let delegate = self.delegate {122delegate.reloadChannelDescriptorList()123}124}125}126}127128129// MARK: - Create channel130131func createChannelWithName(name: String, completion: @escaping (Bool, TCHChannel?) -> Void) {132if (name == ChannelManager.defaultChannelName) {133completion(false, nil)134return135}136137let channelOptions = [138TCHChannelOptionFriendlyName: name,139TCHChannelOptionType: TCHChannelType.public.rawValue140] as [String : Any]141UIApplication.shared.isNetworkActivityIndicatorVisible = true;142self.channelsList?.createChannel(options: channelOptions) { result, channel in143UIApplication.shared.isNetworkActivityIndicatorVisible = false144completion((result.isSuccessful()), channel)145}146}147}148149// MARK: - TwilioChatClientDelegate150extension ChannelManager : TwilioChatClientDelegate {151func chatClient(_ client: TwilioChatClient, channelAdded channel: TCHChannel) {152DispatchQueue.main.async {153self.populateChannelDescriptors()154}155}156157func chatClient(_ client: TwilioChatClient, channel: TCHChannel, updated: TCHChannelUpdate) {158DispatchQueue.main.async {159self.delegate?.reloadChannelDescriptorList()160}161}162163func chatClient(_ client: TwilioChatClient, channelDeleted channel: TCHChannel) {164DispatchQueue.main.async {165self.populateChannelDescriptors()166}167168}169170func chatClient(_ client: TwilioChatClient, synchronizationStatusUpdated status: TCHClientSynchronizationStatus) {171}172}173
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 Programmable Chat client allows you to create private channels and handle invitations.
Once you have joined a channel, you can register a class as the TCHChannelDelegate
so you can start listening to events such as messageAdded
or memberJoined
. We'll show you how to do this in the next step.
twiliochat/ChannelManager.swift
1import UIKit23protocol ChannelManagerDelegate {4func reloadChannelDescriptorList()5}67class ChannelManager: NSObject {8static let sharedManager = ChannelManager()910static let defaultChannelUniqueName = "general"11static let defaultChannelName = "General Channel"1213var delegate:ChannelManagerDelegate?1415var channelsList:TCHChannels?16var channelDescriptors:NSOrderedSet?17var generalChannel:TCHChannel!1819override init() {20super.init()21channelDescriptors = NSMutableOrderedSet()22}2324// MARK: - General channel2526func joinGeneralChatRoomWithCompletion(completion: @escaping (Bool) -> Void) {2728let uniqueName = ChannelManager.defaultChannelUniqueName29if let channelsList = self.channelsList {30channelsList.channel(withSidOrUniqueName: uniqueName) { result, channel in31self.generalChannel = channel3233if self.generalChannel != nil {34self.joinGeneralChatRoomWithUniqueName(name: nil, completion: completion)35} else {36self.createGeneralChatRoomWithCompletion { succeeded in37if (succeeded) {38self.joinGeneralChatRoomWithUniqueName(name: uniqueName, completion: completion)39return40}4142completion(false)43}44}45}46}47}4849func joinGeneralChatRoomWithUniqueName(name: String?, completion: @escaping (Bool) -> Void) {50generalChannel.join { result in51if ((result.isSuccessful()) && name != nil) {52self.setGeneralChatRoomUniqueNameWithCompletion(completion: completion)53return54}55completion((result.isSuccessful()))56}57}5859func createGeneralChatRoomWithCompletion(completion: @escaping (Bool) -> Void) {60let channelName = ChannelManager.defaultChannelName61let options = [62TCHChannelOptionFriendlyName: channelName,63TCHChannelOptionType: TCHChannelType.public.rawValue64] as [String : Any]65channelsList!.createChannel(options: options) { result, channel in66if (result.isSuccessful()) {67self.generalChannel = channel68}69completion((result.isSuccessful()))70}71}7273func setGeneralChatRoomUniqueNameWithCompletion(completion:@escaping (Bool) -> Void) {74generalChannel.setUniqueName(ChannelManager.defaultChannelUniqueName) { result in75completion((result.isSuccessful()))76}77}7879// MARK: - Populate channel Descriptors8081func populateChannelDescriptors() {8283channelsList?.userChannelDescriptors { result, paginator in84guard let paginator = paginator else {85return86}8788let newChannelDescriptors = NSMutableOrderedSet()89newChannelDescriptors.addObjects(from: paginator.items())90self.channelsList?.publicChannelDescriptors { result, paginator in91guard let paginator = paginator else {92return93}9495// de-dupe channel list96let channelIds = NSMutableSet()97for descriptor in newChannelDescriptors {98if let descriptor = descriptor as? TCHChannelDescriptor {99if let sid = descriptor.sid {100channelIds.add(sid)101}102}103}104for descriptor in paginator.items() {105if let sid = descriptor.sid {106if !channelIds.contains(sid) {107channelIds.add(sid)108newChannelDescriptors.add(descriptor)109}110}111}112113114// sort the descriptors115let sortSelector = #selector(NSString.localizedCaseInsensitiveCompare(_:))116let descriptor = NSSortDescriptor(key: "friendlyName", ascending: true, selector: sortSelector)117newChannelDescriptors.sort(using: [descriptor])118119self.channelDescriptors = newChannelDescriptors120121if let delegate = self.delegate {122delegate.reloadChannelDescriptorList()123}124}125}126}127128129// MARK: - Create channel130131func createChannelWithName(name: String, completion: @escaping (Bool, TCHChannel?) -> Void) {132if (name == ChannelManager.defaultChannelName) {133completion(false, nil)134return135}136137let channelOptions = [138TCHChannelOptionFriendlyName: name,139TCHChannelOptionType: TCHChannelType.public.rawValue140] as [String : Any]141UIApplication.shared.isNetworkActivityIndicatorVisible = true;142self.channelsList?.createChannel(options: channelOptions) { result, channel in143UIApplication.shared.isNetworkActivityIndicatorVisible = false144completion((result.isSuccessful()), channel)145}146}147}148149// MARK: - TwilioChatClientDelegate150extension ChannelManager : TwilioChatClientDelegate {151func chatClient(_ client: TwilioChatClient, channelAdded channel: TCHChannel) {152DispatchQueue.main.async {153self.populateChannelDescriptors()154}155}156157func chatClient(_ client: TwilioChatClient, channel: TCHChannel, updated: TCHChannelUpdate) {158DispatchQueue.main.async {159self.delegate?.reloadChannelDescriptorList()160}161}162163func chatClient(_ client: TwilioChatClient, channelDeleted channel: TCHChannel) {164DispatchQueue.main.async {165self.populateChannelDescriptors()166}167168}169170func chatClient(_ client: TwilioChatClient, synchronizationStatusUpdated status: TCHClientSynchronizationStatus) {171}172}173
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:
messageAdded
: When someone sends a message to the channel you are connected to.channelDeleted
: When someone deletes a channel.memberJoined
: When someone joins the channel.memberLeft
: When someone leaves the channel.synchronizationStatusChanged
: When channel synchronization status changes.As you may have noticed, each one of these methods includes useful objects as parameters. One example is the actual message that was added to the channel.
twiliochat/MainChatViewController.swift
1import UIKit2import SlackTextViewController34class MainChatViewController: SLKTextViewController {5static let TWCChatCellIdentifier = "ChatTableCell"6static let TWCChatStatusCellIdentifier = "ChatStatusTableCell"78static let TWCOpenGeneralChannelSegue = "OpenGeneralChat"9static let TWCLabelTag = 2001011var _channel:TCHChannel!12var channel:TCHChannel! {13get {14return _channel15}16set(channel) {17_channel = channel18title = _channel.friendlyName19_channel.delegate = self2021if _channel == ChannelManager.sharedManager.generalChannel {22navigationItem.rightBarButtonItem = nil23}2425joinChannel()26}27}2829var messages:Set<TCHMessage> = Set<TCHMessage>()30var sortedMessages:[TCHMessage]!3132@IBOutlet weak var revealButtonItem: UIBarButtonItem!33@IBOutlet weak var actionButtonItem: UIBarButtonItem!3435override func viewDidLoad() {36super.viewDidLoad()3738if (revealViewController() != nil) {39revealButtonItem.target = revealViewController()40revealButtonItem.action = #selector(SWRevealViewController.revealToggle(_:))41navigationController?.navigationBar.addGestureRecognizer(revealViewController().panGestureRecognizer())42revealViewController().rearViewRevealOverdraw = 043}4445bounces = true46shakeToClearEnabled = true47isKeyboardPanningEnabled = true48shouldScrollToBottomAfterKeyboardShows = false49isInverted = true5051let cellNib = UINib(nibName: MainChatViewController.TWCChatCellIdentifier, bundle: nil)52tableView!.register(cellNib, forCellReuseIdentifier:MainChatViewController.TWCChatCellIdentifier)5354let cellStatusNib = UINib(nibName: MainChatViewController.TWCChatStatusCellIdentifier, bundle: nil)55tableView!.register(cellStatusNib, forCellReuseIdentifier:MainChatViewController.TWCChatStatusCellIdentifier)5657textInputbar.autoHideRightButton = true58textInputbar.maxCharCount = 25659textInputbar.counterStyle = .split60textInputbar.counterPosition = .top6162let font = UIFont(name:"Avenir-Light", size:14)63textView.font = font6465rightButton.setTitleColor(UIColor(red:0.973, green:0.557, blue:0.502, alpha:1), for: .normal)6667if let font = UIFont(name:"Avenir-Heavy", size:17) {68navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.font: font]69}7071tableView!.allowsSelection = false72tableView!.estimatedRowHeight = 7073tableView!.rowHeight = UITableView.automaticDimension74tableView!.separatorStyle = .none7576if channel == nil {77channel = ChannelManager.sharedManager.generalChannel78}79}8081override func viewDidLayoutSubviews() {82super.viewDidLayoutSubviews()8384// required for iOS 1185textInputbar.bringSubviewToFront(textInputbar.textView)86textInputbar.bringSubviewToFront(textInputbar.leftButton)87textInputbar.bringSubviewToFront(textInputbar.rightButton)8889}9091override func viewDidAppear(_ animated: Bool) {92super.viewDidAppear(animated)93scrollToBottom()94}9596override func numberOfSections(in tableView: UITableView) -> Int {97return 198}99100override func tableView(_ tableView: UITableView, numberOfRowsInSection section: NSInteger) -> Int {101return messages.count102}103104override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {105var cell:UITableViewCell106107let message = sortedMessages[indexPath.row]108109if let statusMessage = message as? StatusMessage {110cell = getStatusCellForTableView(tableView: tableView, forIndexPath:indexPath, message:statusMessage)111}112else {113cell = getChatCellForTableView(tableView: tableView, forIndexPath:indexPath, message:message)114}115116cell.transform = tableView.transform117return cell118}119120func getChatCellForTableView(tableView: UITableView, forIndexPath indexPath:IndexPath, message: TCHMessage) -> UITableViewCell {121let cell = tableView.dequeueReusableCell(withIdentifier: MainChatViewController.TWCChatCellIdentifier, for:indexPath as IndexPath)122123let chatCell: ChatTableCell = cell as! ChatTableCell124let date = NSDate.dateWithISO8601String(dateString: message.dateCreated ?? "")125let timestamp = DateTodayFormatter().stringFromDate(date: date)126127chatCell.setUser(user: message.author ?? "[Unknown author]", message: message.body, date: timestamp ?? "[Unknown date]")128129return chatCell130}131132func getStatusCellForTableView(tableView: UITableView, forIndexPath indexPath:IndexPath, message: StatusMessage) -> UITableViewCell {133let cell = tableView.dequeueReusableCell(withIdentifier: MainChatViewController.TWCChatStatusCellIdentifier, for:indexPath as IndexPath)134135let label = cell.viewWithTag(MainChatViewController.TWCLabelTag) as! UILabel136let memberStatus = (message.status! == .Joined) ? "joined" : "left"137label.text = "User \(message.statusMember.identity ?? "[Unknown user]") has \(memberStatus)"138return cell139}140141func joinChannel() {142setViewOnHold(onHold: true)143144if channel.status != .joined {145channel.join { result in146print("Channel Joined")147}148return149}150151loadMessages()152setViewOnHold(onHold: false)153}154155// Disable user input and show activity indicator156func setViewOnHold(onHold: Bool) {157self.isTextInputbarHidden = onHold;158UIApplication.shared.isNetworkActivityIndicatorVisible = onHold;159}160161override func didPressRightButton(_ sender: Any!) {162textView.refreshFirstResponder()163sendMessage(inputMessage: textView.text)164super.didPressRightButton(sender)165}166167// MARK: - Chat Service168169func sendMessage(inputMessage: String) {170let messageOptions = TCHMessageOptions().withBody(inputMessage)171channel.messages?.sendMessage(with: messageOptions, completion: nil)172}173174func addMessages(newMessages:Set<TCHMessage>) {175messages = messages.union(newMessages)176sortMessages()177DispatchQueue.main.async {178self.tableView!.reloadData()179if self.messages.count > 0 {180self.scrollToBottom()181}182}183}184185func sortMessages() {186sortedMessages = messages.sorted { (a, b) -> Bool in187(a.dateCreated ?? "") > (b.dateCreated ?? "")188}189}190191func loadMessages() {192messages.removeAll()193if channel.synchronizationStatus == .all {194channel.messages?.getLastWithCount(100) { (result, items) in195self.addMessages(newMessages: Set(items!))196}197}198}199200func scrollToBottom() {201if messages.count > 0 {202let indexPath = IndexPath(row: 0, section: 0)203tableView!.scrollToRow(at: indexPath, at: .bottom, animated: true)204}205}206207func leaveChannel() {208channel.leave { result in209if (result.isSuccessful()) {210let menuViewController = self.revealViewController().rearViewController as! MenuViewController211menuViewController.deselectSelectedChannel()212self.revealViewController().rearViewController.performSegue(withIdentifier: MainChatViewController.TWCOpenGeneralChannelSegue, sender: nil)213}214}215}216217// MARK: - Actions218219@IBAction func actionButtonTouched(_ sender: UIBarButtonItem) {220leaveChannel()221}222223@IBAction func revealButtonTouched(_ sender: AnyObject) {224revealViewController().revealToggle(animated: true)225}226}227228extension MainChatViewController : TCHChannelDelegate {229func chatClient(_ client: TwilioChatClient, channel: TCHChannel, messageAdded message: TCHMessage) {230if !messages.contains(message) {231addMessages(newMessages: [message])232}233}234235func chatClient(_ client: TwilioChatClient, channel: TCHChannel, memberJoined member: TCHMember) {236addMessages(newMessages: [StatusMessage(statusMember:member, status:.Joined)])237}238239func chatClient(_ client: TwilioChatClient, channel: TCHChannel, memberLeft member: TCHMember) {240addMessages(newMessages: [StatusMessage(statusMember:member, status:.Left)])241}242243func chatClient(_ client: TwilioChatClient, channelDeleted channel: TCHChannel) {244DispatchQueue.main.async {245if channel == self.channel {246self.revealViewController().rearViewController247.performSegue(withIdentifier: MainChatViewController.TWCOpenGeneralChannelSegue, sender: nil)248}249}250}251252func chatClient(_ client: TwilioChatClient,253channel: TCHChannel,254synchronizationStatusUpdated status: TCHChannelSynchronizationStatus) {255if status == .all {256loadMessages()257DispatchQueue.main.async {258self.tableView?.reloadData()259self.setViewOnHold(onHold: false)260}261}262}263}264
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 joinChannel
method takes care of joining to the selected channel and loading the messages.
twiliochat/MainChatViewController.swift
1import UIKit2import SlackTextViewController34class MainChatViewController: SLKTextViewController {5static let TWCChatCellIdentifier = "ChatTableCell"6static let TWCChatStatusCellIdentifier = "ChatStatusTableCell"78static let TWCOpenGeneralChannelSegue = "OpenGeneralChat"9static let TWCLabelTag = 2001011var _channel:TCHChannel!12var channel:TCHChannel! {13get {14return _channel15}16set(channel) {17_channel = channel18title = _channel.friendlyName19_channel.delegate = self2021if _channel == ChannelManager.sharedManager.generalChannel {22navigationItem.rightBarButtonItem = nil23}2425joinChannel()26}27}2829var messages:Set<TCHMessage> = Set<TCHMessage>()30var sortedMessages:[TCHMessage]!3132@IBOutlet weak var revealButtonItem: UIBarButtonItem!33@IBOutlet weak var actionButtonItem: UIBarButtonItem!3435override func viewDidLoad() {36super.viewDidLoad()3738if (revealViewController() != nil) {39revealButtonItem.target = revealViewController()40revealButtonItem.action = #selector(SWRevealViewController.revealToggle(_:))41navigationController?.navigationBar.addGestureRecognizer(revealViewController().panGestureRecognizer())42revealViewController().rearViewRevealOverdraw = 043}4445bounces = true46shakeToClearEnabled = true47isKeyboardPanningEnabled = true48shouldScrollToBottomAfterKeyboardShows = false49isInverted = true5051let cellNib = UINib(nibName: MainChatViewController.TWCChatCellIdentifier, bundle: nil)52tableView!.register(cellNib, forCellReuseIdentifier:MainChatViewController.TWCChatCellIdentifier)5354let cellStatusNib = UINib(nibName: MainChatViewController.TWCChatStatusCellIdentifier, bundle: nil)55tableView!.register(cellStatusNib, forCellReuseIdentifier:MainChatViewController.TWCChatStatusCellIdentifier)5657textInputbar.autoHideRightButton = true58textInputbar.maxCharCount = 25659textInputbar.counterStyle = .split60textInputbar.counterPosition = .top6162let font = UIFont(name:"Avenir-Light", size:14)63textView.font = font6465rightButton.setTitleColor(UIColor(red:0.973, green:0.557, blue:0.502, alpha:1), for: .normal)6667if let font = UIFont(name:"Avenir-Heavy", size:17) {68navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.font: font]69}7071tableView!.allowsSelection = false72tableView!.estimatedRowHeight = 7073tableView!.rowHeight = UITableView.automaticDimension74tableView!.separatorStyle = .none7576if channel == nil {77channel = ChannelManager.sharedManager.generalChannel78}79}8081override func viewDidLayoutSubviews() {82super.viewDidLayoutSubviews()8384// required for iOS 1185textInputbar.bringSubviewToFront(textInputbar.textView)86textInputbar.bringSubviewToFront(textInputbar.leftButton)87textInputbar.bringSubviewToFront(textInputbar.rightButton)8889}9091override func viewDidAppear(_ animated: Bool) {92super.viewDidAppear(animated)93scrollToBottom()94}9596override func numberOfSections(in tableView: UITableView) -> Int {97return 198}99100override func tableView(_ tableView: UITableView, numberOfRowsInSection section: NSInteger) -> Int {101return messages.count102}103104override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {105var cell:UITableViewCell106107let message = sortedMessages[indexPath.row]108109if let statusMessage = message as? StatusMessage {110cell = getStatusCellForTableView(tableView: tableView, forIndexPath:indexPath, message:statusMessage)111}112else {113cell = getChatCellForTableView(tableView: tableView, forIndexPath:indexPath, message:message)114}115116cell.transform = tableView.transform117return cell118}119120func getChatCellForTableView(tableView: UITableView, forIndexPath indexPath:IndexPath, message: TCHMessage) -> UITableViewCell {121let cell = tableView.dequeueReusableCell(withIdentifier: MainChatViewController.TWCChatCellIdentifier, for:indexPath as IndexPath)122123let chatCell: ChatTableCell = cell as! ChatTableCell124let date = NSDate.dateWithISO8601String(dateString: message.dateCreated ?? "")125let timestamp = DateTodayFormatter().stringFromDate(date: date)126127chatCell.setUser(user: message.author ?? "[Unknown author]", message: message.body, date: timestamp ?? "[Unknown date]")128129return chatCell130}131132func getStatusCellForTableView(tableView: UITableView, forIndexPath indexPath:IndexPath, message: StatusMessage) -> UITableViewCell {133let cell = tableView.dequeueReusableCell(withIdentifier: MainChatViewController.TWCChatStatusCellIdentifier, for:indexPath as IndexPath)134135let label = cell.viewWithTag(MainChatViewController.TWCLabelTag) as! UILabel136let memberStatus = (message.status! == .Joined) ? "joined" : "left"137label.text = "User \(message.statusMember.identity ?? "[Unknown user]") has \(memberStatus)"138return cell139}140141func joinChannel() {142setViewOnHold(onHold: true)143144if channel.status != .joined {145channel.join { result in146print("Channel Joined")147}148return149}150151loadMessages()152setViewOnHold(onHold: false)153}154155// Disable user input and show activity indicator156func setViewOnHold(onHold: Bool) {157self.isTextInputbarHidden = onHold;158UIApplication.shared.isNetworkActivityIndicatorVisible = onHold;159}160161override func didPressRightButton(_ sender: Any!) {162textView.refreshFirstResponder()163sendMessage(inputMessage: textView.text)164super.didPressRightButton(sender)165}166167// MARK: - Chat Service168169func sendMessage(inputMessage: String) {170let messageOptions = TCHMessageOptions().withBody(inputMessage)171channel.messages?.sendMessage(with: messageOptions, completion: nil)172}173174func addMessages(newMessages:Set<TCHMessage>) {175messages = messages.union(newMessages)176sortMessages()177DispatchQueue.main.async {178self.tableView!.reloadData()179if self.messages.count > 0 {180self.scrollToBottom()181}182}183}184185func sortMessages() {186sortedMessages = messages.sorted { (a, b) -> Bool in187(a.dateCreated ?? "") > (b.dateCreated ?? "")188}189}190191func loadMessages() {192messages.removeAll()193if channel.synchronizationStatus == .all {194channel.messages?.getLastWithCount(100) { (result, items) in195self.addMessages(newMessages: Set(items!))196}197}198}199200func scrollToBottom() {201if messages.count > 0 {202let indexPath = IndexPath(row: 0, section: 0)203tableView!.scrollToRow(at: indexPath, at: .bottom, animated: true)204}205}206207func leaveChannel() {208channel.leave { result in209if (result.isSuccessful()) {210let menuViewController = self.revealViewController().rearViewController as! MenuViewController211menuViewController.deselectSelectedChannel()212self.revealViewController().rearViewController.performSegue(withIdentifier: MainChatViewController.TWCOpenGeneralChannelSegue, sender: nil)213}214}215}216217// MARK: - Actions218219@IBAction func actionButtonTouched(_ sender: UIBarButtonItem) {220leaveChannel()221}222223@IBAction func revealButtonTouched(_ sender: AnyObject) {224revealViewController().revealToggle(animated: true)225}226}227228extension MainChatViewController : TCHChannelDelegate {229func chatClient(_ client: TwilioChatClient, channel: TCHChannel, messageAdded message: TCHMessage) {230if !messages.contains(message) {231addMessages(newMessages: [message])232}233}234235func chatClient(_ client: TwilioChatClient, channel: TCHChannel, memberJoined member: TCHMember) {236addMessages(newMessages: [StatusMessage(statusMember:member, status:.Joined)])237}238239func chatClient(_ client: TwilioChatClient, channel: TCHChannel, memberLeft member: TCHMember) {240addMessages(newMessages: [StatusMessage(statusMember:member, status:.Left)])241}242243func chatClient(_ client: TwilioChatClient, channelDeleted channel: TCHChannel) {244DispatchQueue.main.async {245if channel == self.channel {246self.revealViewController().rearViewController247.performSegue(withIdentifier: MainChatViewController.TWCOpenGeneralChannelSegue, sender: nil)248}249}250}251252func chatClient(_ client: TwilioChatClient,253channel: TCHChannel,254synchronizationStatusUpdated status: TCHChannelSynchronizationStatus) {255if status == .all {256loadMessages()257DispatchQueue.main.async {258self.tableView?.reloadData()259self.setViewOnHold(onHold: false)260}261}262}263}264
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 involves calling createChannel
and passing a dictionary with the new channel information.
twiliochat/ChannelManager.swift
1import UIKit23protocol ChannelManagerDelegate {4func reloadChannelDescriptorList()5}67class ChannelManager: NSObject {8static let sharedManager = ChannelManager()910static let defaultChannelUniqueName = "general"11static let defaultChannelName = "General Channel"1213var delegate:ChannelManagerDelegate?1415var channelsList:TCHChannels?16var channelDescriptors:NSOrderedSet?17var generalChannel:TCHChannel!1819override init() {20super.init()21channelDescriptors = NSMutableOrderedSet()22}2324// MARK: - General channel2526func joinGeneralChatRoomWithCompletion(completion: @escaping (Bool) -> Void) {2728let uniqueName = ChannelManager.defaultChannelUniqueName29if let channelsList = self.channelsList {30channelsList.channel(withSidOrUniqueName: uniqueName) { result, channel in31self.generalChannel = channel3233if self.generalChannel != nil {34self.joinGeneralChatRoomWithUniqueName(name: nil, completion: completion)35} else {36self.createGeneralChatRoomWithCompletion { succeeded in37if (succeeded) {38self.joinGeneralChatRoomWithUniqueName(name: uniqueName, completion: completion)39return40}4142completion(false)43}44}45}46}47}4849func joinGeneralChatRoomWithUniqueName(name: String?, completion: @escaping (Bool) -> Void) {50generalChannel.join { result in51if ((result.isSuccessful()) && name != nil) {52self.setGeneralChatRoomUniqueNameWithCompletion(completion: completion)53return54}55completion((result.isSuccessful()))56}57}5859func createGeneralChatRoomWithCompletion(completion: @escaping (Bool) -> Void) {60let channelName = ChannelManager.defaultChannelName61let options = [62TCHChannelOptionFriendlyName: channelName,63TCHChannelOptionType: TCHChannelType.public.rawValue64] as [String : Any]65channelsList!.createChannel(options: options) { result, channel in66if (result.isSuccessful()) {67self.generalChannel = channel68}69completion((result.isSuccessful()))70}71}7273func setGeneralChatRoomUniqueNameWithCompletion(completion:@escaping (Bool) -> Void) {74generalChannel.setUniqueName(ChannelManager.defaultChannelUniqueName) { result in75completion((result.isSuccessful()))76}77}7879// MARK: - Populate channel Descriptors8081func populateChannelDescriptors() {8283channelsList?.userChannelDescriptors { result, paginator in84guard let paginator = paginator else {85return86}8788let newChannelDescriptors = NSMutableOrderedSet()89newChannelDescriptors.addObjects(from: paginator.items())90self.channelsList?.publicChannelDescriptors { result, paginator in91guard let paginator = paginator else {92return93}9495// de-dupe channel list96let channelIds = NSMutableSet()97for descriptor in newChannelDescriptors {98if let descriptor = descriptor as? TCHChannelDescriptor {99if let sid = descriptor.sid {100channelIds.add(sid)101}102}103}104for descriptor in paginator.items() {105if let sid = descriptor.sid {106if !channelIds.contains(sid) {107channelIds.add(sid)108newChannelDescriptors.add(descriptor)109}110}111}112113114// sort the descriptors115let sortSelector = #selector(NSString.localizedCaseInsensitiveCompare(_:))116let descriptor = NSSortDescriptor(key: "friendlyName", ascending: true, selector: sortSelector)117newChannelDescriptors.sort(using: [descriptor])118119self.channelDescriptors = newChannelDescriptors120121if let delegate = self.delegate {122delegate.reloadChannelDescriptorList()123}124}125}126}127128129// MARK: - Create channel130131func createChannelWithName(name: String, completion: @escaping (Bool, TCHChannel?) -> Void) {132if (name == ChannelManager.defaultChannelName) {133completion(false, nil)134return135}136137let channelOptions = [138TCHChannelOptionFriendlyName: name,139TCHChannelOptionType: TCHChannelType.public.rawValue140] as [String : Any]141UIApplication.shared.isNetworkActivityIndicatorVisible = true;142self.channelsList?.createChannel(options: channelOptions) { result, channel in143UIApplication.shared.isNetworkActivityIndicatorVisible = false144completion((result.isSuccessful()), channel)145}146}147}148149// MARK: - TwilioChatClientDelegate150extension ChannelManager : TwilioChatClientDelegate {151func chatClient(_ client: TwilioChatClient, channelAdded channel: TCHChannel) {152DispatchQueue.main.async {153self.populateChannelDescriptors()154}155}156157func chatClient(_ client: TwilioChatClient, channel: TCHChannel, updated: TCHChannelUpdate) {158DispatchQueue.main.async {159self.delegate?.reloadChannelDescriptorList()160}161}162163func chatClient(_ client: TwilioChatClient, channelDeleted channel: TCHChannel) {164DispatchQueue.main.async {165self.populateChannelDescriptors()166}167168}169170func chatClient(_ client: TwilioChatClient, synchronizationStatusUpdated status: TCHClientSynchronizationStatus) {171}172}173
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), call the channel's method destroy
.
twiliochat/MenuViewController.swift
1import UIKit23class MenuViewController: UIViewController {4static let TWCOpenChannelSegue = "OpenChat"5static let TWCRefreshControlXOffset: CGFloat = 12067@IBOutlet weak var tableView: UITableView!8@IBOutlet weak var usernameLabel: UILabel!910var refreshControl: UIRefreshControl!1112override func viewDidLoad() {13super.viewDidLoad()1415let bgImage = UIImageView(image: UIImage(named:"home-bg"))16bgImage.frame = self.tableView.frame17tableView.backgroundView = bgImage1819usernameLabel.text = MessagingManager.sharedManager().userIdentity2021refreshControl = UIRefreshControl()22tableView.addSubview(refreshControl)23refreshControl.addTarget(self, action: #selector(MenuViewController.refreshChannels), for: .valueChanged)24refreshControl.tintColor = UIColor.white2526self.refreshControl.frame.origin.x -= MenuViewController.TWCRefreshControlXOffset27ChannelManager.sharedManager.delegate = self28tableView.reloadData()29}3031// MARK: - Internal methods3233func loadingCellForTableView(tableView: UITableView) -> UITableViewCell {34return tableView.dequeueReusableCell(withIdentifier: "loadingCell")!35}3637func channelCellForTableView(tableView: UITableView, atIndexPath indexPath: NSIndexPath) -> UITableViewCell {38let menuCell = tableView.dequeueReusableCell(withIdentifier: "channelCell", for: indexPath as IndexPath) as! MenuTableCell3940if let channelDescriptor = ChannelManager.sharedManager.channelDescriptors![indexPath.row] as? TCHChannelDescriptor {41menuCell.channelName = channelDescriptor.friendlyName ?? "[Unknown channel name]"42} else {43menuCell.channelName = "[Unknown channel name]"44}4546return menuCell47}4849@objc func refreshChannels() {50refreshControl.beginRefreshing()51tableView.reloadData()52refreshControl.endRefreshing()53}5455func deselectSelectedChannel() {56let selectedRow = tableView.indexPathForSelectedRow57if let row = selectedRow {58tableView.deselectRow(at: row, animated: true)59}60}6162// MARK: - Channel6364func createNewChannelDialog() {65InputDialogController.showWithTitle(title: "New Channel",66message: "Enter a name for this channel",67placeholder: "Name",68presenter: self) { text in69ChannelManager.sharedManager.createChannelWithName(name: text, completion: { _,_ in70ChannelManager.sharedManager.populateChannelDescriptors()71})72}73}7475// MARK: Logout7677func promptLogout() {78let alert = UIAlertController(title: nil, message: "You are about to Logout", preferredStyle: .alert)7980let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)81let confirmAction = UIAlertAction(title: "Confirm", style: .default) { action in82self.logOut()83}8485alert.addAction(cancelAction)86alert.addAction(confirmAction)87present(alert, animated: true, completion: nil)88}8990func logOut() {91MessagingManager.sharedManager().logout()92MessagingManager.sharedManager().presentRootViewController()93}9495// MARK: - Actions9697@IBAction func logoutButtonTouched(_ sender: UIButton) {98promptLogout()99}100101@IBAction func newChannelButtonTouched(_ sender: UIButton) {102createNewChannelDialog()103}104105// MARK: - Navigation106107override func prepare(for segue: UIStoryboardSegue, sender: Any?) {108if segue.identifier == MenuViewController.TWCOpenChannelSegue {109let indexPath = sender as! NSIndexPath110111let channelDescriptor = ChannelManager.sharedManager.channelDescriptors![indexPath.row] as! TCHChannelDescriptor112let navigationController = segue.destination as! UINavigationController113114channelDescriptor.channel { (result, channel) in115if let channel = channel {116(navigationController.visibleViewController as! MainChatViewController).channel = channel117}118}119120}121}122123// MARK: - Style124125override var preferredStatusBarStyle: UIStatusBarStyle {126return .lightContent127}128}129130// MARK: - UITableViewDataSource131extension MenuViewController : UITableViewDataSource {132func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {133if let channelDescriptors = ChannelManager.sharedManager.channelDescriptors {134print (channelDescriptors.count)135return channelDescriptors.count136}137return 1138}139140func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {141let cell: UITableViewCell142143if ChannelManager.sharedManager.channelDescriptors == nil {144cell = loadingCellForTableView(tableView: tableView)145}146else {147cell = channelCellForTableView(tableView: tableView, atIndexPath: indexPath as NSIndexPath)148}149150cell.layoutIfNeeded()151return cell152}153154func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {155if let channel = ChannelManager.sharedManager.channelDescriptors?.object(at: indexPath.row) as? TCHChannel {156return channel != ChannelManager.sharedManager.generalChannel157}158return false159}160161func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle,162forRowAt indexPath: IndexPath) {163if editingStyle != .delete {164return165}166if let channel = ChannelManager.sharedManager.channelDescriptors?.object(at: indexPath.row) as? TCHChannel {167channel.destroy { result in168if (result.isSuccessful()) {169tableView.reloadData()170}171else {172AlertDialogController.showAlertWithMessage(message: "You can not delete this channel", title: nil, presenter: self)173}174}175}176}177}178179// MARK: - UITableViewDelegate180extension MenuViewController : UITableViewDelegate {181func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {182tableView.deselectRow(at: indexPath, animated: true)183performSegue(withIdentifier: MenuViewController.TWCOpenChannelSegue, sender: indexPath)184}185}186187188// MARK: - ChannelManagerDelegate189extension MenuViewController : ChannelManagerDelegate {190func reloadChannelDescriptorList() {191tableView.reloadData()192}193}194
That's it! We've built an iOS application with Swift. Now you are more than prepared to set up your own chat application.
If you are an iOS developer working with Twilio, you might want to check out this other project:
Twilio Notifications for iOS Quickstart using Swift
Thanks for checking out this tutorial! If you have any feedback to share with us, we'd love to hear it. Tweet @twilio to let us know what you think.