Build a complete iOS messaging app using XMPPFramework – Part 2
- Erlang Solutions Team
- 12th Jan 2017
- 13 min of reading time
Build a complete iOS messaging app using XMPPFramework is a tutorial that shows you how to build a fully functional instant messaging iOS app using the very cool XMPPFramework protocol and Swift3. In this part, we are going to get our hands dirty! To recap on the theory, or if you just landed here randomly, have a quick read through the first part, then get your Xcode ready and let’s start!
In this issue we are going to be integrating the library to our project, creating a connection with the server and authenticating. The XMPPFramework library is the most used XMPP library for iOS and macOS. At the beginning it may be a little bit overwhelming but after a few days working with it you will learn to love it.
Let’s create a brand new Xcode project and install the library. In this tutorial we are going to be using Swift 3. The easiest way to integrate XMPPFramework to the project is using CocoaPods.
Let’s create our Podfile
using the pod init command in the folder where our .xcodeproj
lives. There are thousands of forks but the maintained one is the original: robbiehanson/XMPPFramework.
So let’s add the pod
to our Podfile
and remember to uncomment the use_frameworks!
.
use_frameworks!
target 'CrazyMessages' do
pod 'XMPPFramework', :git=> 'git@github.com:robbiehanson/XMPPFramework.git', :branch => 'master'
end
Then pod install
and CocoaPods
is going to do its magic and create a .xcworkspace
with the library integrated. Now we just need to import XMPPFramework
in the files we want to use the library and that’s it.
The most important thing in an XMPP application is the stream, that’s where we are going to “write” our stanzas, so we need an object that is going to hold it. We are going to create an XMPPController
class with an XMPPStream
:
import Foundation
import XMPPFramework
class XMPPController: NSObject {
var xmppStream: XMPPStream
init() {
self.xmppStream = XMPPStream()
}
}
We are dealing with a highly asynchronous library here. For every action we are going to have a response some time in the future. To handle this XMPPFramework
defines the XMPPStreamDelegate
. So implementing that delegate is going to help us answer lots of different questions like: “How do I know when XMPP has successfully connected?”, “How do I know if I’m correctly authenticated?”, “How do I know if I received a message?”. XMPPStreamDelegate
is your friend!
So we have our XMPPController
and our XMPPStream
, what do we need to do now? Configure our stream with the hostName
, port
and ourJID
. To provide all this info to the controller we are going to make some changes to the init
to be able to receive all these parameters:
enum XMPPControllerError: Error {
case wrongUserJID
}
class XMPPController: NSObject {
var xmppStream: XMPPStream
let hostName: String
let userJID: XMPPJID
let hostPort: UInt16
let password: String
init(hostName: String, userJIDString: String, hostPort: UInt16 = 5222, password: String) throws {
guard let userJID = XMPPJID(string: userJIDString) else {
throw XMPPControllerError.wrongUserJID
}
self.hostName = hostName
self.userJID = userJID
self.hostPort = hostPort
self.password = password
// Stream Configuration
self.xmppStream = XMPPStream()
self.xmppStream.hostName = hostName
self.xmppStream.hostPort = hostPort
self.xmppStream.startTLSPolicy = XMPPStreamStartTLSPolicy.allowed
self.xmppStream.myJID = userJID
super.init()
self.xmppStream.addDelegate(self, delegateQueue: DispatchQueue.main)
}
}
Our next step is going to actually connect to a server and authenticate using our userJID
and password
, so we are adding a connect
method to our XMPPController
.
func connect() {
if !self.xmppStream.isDisconnected() {
return
}
try! self.xmppStream.connect(withTimeout: XMPPStreamTimeoutNone)
}
But how do we know we have successfully connected to the server? As I said earlier, we need to check for a suitable delegate method from XMPPStreamDelegate
. After we connect to the server we need to authenticate so we are going to do the following:
extension XMPPController: XMPPStreamDelegate {
func xmppStreamDidConnect(_ stream: XMPPStream!) {
print("Stream: Connected")
try! stream.authenticate(withPassword: self.password)
}
func xmppStreamDidAuthenticate(_ sender: XMPPStream!) {
self.xmppStream.send(XMPPPresence())
print("Stream: Authenticated")
}
}
We need to test this. Let’s just create an instance of XMPPController
in the AppDelegate
to test how it works:
try! self.xmppController = XMPPController(hostName: "host.com",
userJIDString: "user@host.com",
password: "password")
self.xmppController.connect()
If everything goes fine we should see two messages in the logs but of course that’s not happening, we missed something. We never told to our xmppStream
who was the delegate object! We need to add the following line after the super.init()
self.xmppStream.addDelegate(self, delegateQueue: DispatchQueue.main)
If we run the app again:
Stream: Connected
Stream: Authenticated
Success! We have our own XMPPController
with a fully functional and authenticated stream!
Something that may catch your attention is how we are setting our delegate, we are not doing:
self.xmppStream.delegate = self
Why not? Because we can “broadcast” the events to multiple delegates, we can have 10 different objects implementing those methods. Also we can tell what’s the thread where we want to receive that call, in the previous example we want it in the main thread.
Our app is super ugly, let’s put on some makeup! We have nothing but an XMPPController
and a hardcoded call in the AppDelegate
. I’m going to create a ViewController
that is going to be presented modally as soon as the app starts, that ViewController
will have the neccesary fields/info to log in to the server.
I’m going to create a LogInViewControllerDelegate
that is going to tell to our ViewController
that the Log in
button was pressed and that’s it. In that delegate implementation we are going to create our XMPPController
, add the ViewController
as delegate of the XMPPStream
and connect!
extension ViewController: LogInViewControllerDelegate {
func didTouchLogIn(sender: LogInViewController, userJID: String, userPassword: String, server: String) {
self.logInViewController = sender
do {
try self.xmppController = XMPPController(hostName: server,
userJIDString: userJID,
password: userPassword)
self.xmppController.xmppStream.addDelegate(self, delegateQueue: DispatchQueue.main)
self.xmppController.connect()
} catch {
sender.showErrorMessage(message: "Something went wrong")
}
}
}
Why are we adding ViewController
as a delegate of XMPPStream
if our XMPPController
alreay has that delegate implemented? Because we need to know if this connection and authentication was successfull or not in our ViewController
so we are able to dismiss the LogInViewController
or show an error message if something failed. This is why being able to add multiple delegates is so useful.
So as I said I’m going to make ViewController
to comform to the XMPPStreamDelegate
:
extension ViewController: XMPPStreamDelegate {
func xmppStreamDidAuthenticate(_ sender: XMPPStream!) {
self.logInViewController?.dismiss(animated: true, completion: nil)
}
func xmppStream(_ sender: XMPPStream!, didNotAuthenticate error: DDXMLElement!) {
self.logInViewController?.showErrorMessage(message: "Wrong password or username")
}
}
And that’s it! Our app can log in to our server as I’m showing here:
We’ve been talking a lot about XMPP, stanzas and streams… but is there a way I can see the stream? Yes SR! XMPPFramework got us covered!
XMPPFramework ships with CocoaLumberJack, a pretty well known logging framework. We just need to configure it, set the logging level we want and that’s it. Logs are going to start showing up!
This is a really simple task, you just need to add to your func application(application: UIApplication, didFinishLaunchingWithOptions ...
method the following line (remember to import CocoaLumberjack
):
DDLog.add(DDTTYLogger.sharedInstance(), with: DDLogLevel.all)
I’m not going to paste here all the connection process log because it makes no sense to try to understand what’s going on at this stage of our learning. But I think showing what some stanzas look like is a good idea. To do this I’m going to be sending messages from Adium.
I’m going to send this <message/>
:
<message to="test.user@erlang-solutions.com">
<body>This is a message sent from Adium!</body>
</message>
Let’s see how it looks like when it reaches our app:
<message xmlns="jabber:client" from="iamadium@erlang-solutions.com/MacBook-Air" to="test.user@erlang-solutions.com">
<body>This is a message sent from Adium!</body>
</message>
Let’s send a <presence/>
from Adium:
<presence>
<status>On vacation</status>
</presence>
We are receiving:
<presence xmlns="jabber:client" from="iamadium@erlang-solutions.com/MacBook-Air" to="test.user@erlang-solutions.com">
<status>On vacation</status>
</presence>
No doubts at all right? We send something and we receive it on the other end! That’s it!
I want to be sure that you are understanding and following everything and not just copy and pasting from a tutorial (as I usually do 🙊). So if you are able to answer these questions you are on a good track!
If you need help leave a message!
The sample project is on Github
The next part is going to be on Roster
, and if I will have space I would also like to add sending and receiving messages. I’ve been kind of super busy lately so I’m not sure when I’m going to be able to deliver the next issue but I’ll try to work on it as soon as I have some free minutes to spare!
PS: Also take a look at MongooseIM, our XMPP based open source mobile messaging platform.
This is an XMPP tutorial from an iOS developer’s perspective. This journey is going to go from no XMPP knowledge at all to having a fully functional instant messaging iOS app using this cool protocol.
Our team look at the common mistakes they see from their clients and how you can avoid them to ensure your chat functionality is a success.
How MongooseIM offers privacy by default and control by design.