Apple announced that Swift would become open source at the WWDC keynote (earlier this week). The perfect moment for me to create my very first iOS app (in the newly released Swift 2 and Xcode 7 beta). So I spent a few hours yesterday and created a real-time live blog app as a front-end for the WebSocket Liveblog back-end I’ve written in Go.
Xcode project
First I downloaded the new Xcode 7 beta, and created a new application. File, New, Project. Select iOS, Single View Application. Fill in the needed information (make sure the select Swift as programming language and not Objective-C) and create the project.
CocoaPods
Since I didn’t really figured out how to import external modules from Github (and didn’t want to lose time on doing so) I used the CocoaPods tool. This handy tool manages the dependencies for you. (Comparable to Composer for PHP)
Installing CocoaPods is very simple using the Terminal:
$ sudo gem install cocoapods
In Terminal navigate to your project folder and execute pod init
(This will create the Podfile
file in the root directory of your project), and append the following content the Podfile:
use_frameworks!
target 'Liveblog' do
pod 'Starscream', '~> 0.9'
end
Now open the Liveblog.xcworkspace
Workspace in Xcode. Since Starscream isn’t yet coded in Swift 2, Xcode 7 will ask the convert the code to Swift 2. After the automatic conversion, there will still be some compile errors to fix. You can fix them with auto-fix in Xcode.
Creating the user interface
Next step is designing our iPhone app. (I’m not a designer, nor an UX expert, so I’ll keep it simple, KISS). All of this happens in the Main.storyboard
file.
At the bottom of the right sidebar, there are some switches with pre-made things you can include into your project (including code snippets, or creating new files). We need the third tab of that panel, i.e. the panel with UX elements.
View
Our storyboard will already consist of a View, which we can style. I chose to give the main view a sea green background.
Label
Now it’s time to create a title at the top of the view. Drag a label from the right bottom UX panel to the view. Again you can do some styling at the right panel. (I increased the font size a lot, and opted for a white font)
Now we want to center the label on all screen sizes. Therefor you can add ‘responsive’ constraints to your storyboard elements. At the right bottom corner of the storyboard panel, there are 4 buttons. Click on the second button to add aligning constraints: select the horizontal constraint and click on “Add 1 constraint”.
Text View
The same steps need to be followed to create a Text View on the storyboard. Drag the Text View element from the right bottom panel to the storyboard and resize it so it fills the whole view (except the top bar). Again, we want this UI element to fit all screen sizes, so we must add some constraints so it will always cover the whole screen.
Click on the red dashed lines in the constraints panel to ‘enable’ the constraints, and then click on “Add 4 constraints”.
Now launch the app in the iOS simulator and make sure everything is responsive. If everything looks fine, you can remove the sample text from the TextView (since we will load WebSocket data in it)
Add referencing outlet
If we want to actually do something with the TextView, we need to create a referencing outlet for it in the ViewController.swift
file. In the right top navigation click on the editor icon (the two overlapping rings). This will open a second, split view with the code of the ViewController. Now press the ctrl key and drag the TextView into the code at the right side and give the outlet a name.
If completed successfully, your ViewController class will look like this:
class ViewController: UIViewController {
@IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Adding the WebSockets in Swift
Now we can finally implement the actual WebSockets for our app. Well, not really implementing, because we will be using Starscream’s WebSocket client (as mentioned above in the section of the CocoaPods)
First thing to do is import Starscream above the ViewController class. Next add WebSocketDelegate
in the class declaration line to indicate we are implementing the Starscream WebSocketDelegate
interface (so we can delegate the WebSocket to the ViewController).
class ViewController: UIViewController, WebSocketDelegate {
// ...
}
Since we defined that ViewController implements the WebSocketDelegate
interface, you need to add the functions of that interface to the ViewController:
// websocketDidConnect is called as soon as the client connects to the server.
func websocketDidConnect(socket: WebSocket) {
println("websocket is connected")
}
// websocketDidDisconnect is called as soon as the client is disconnected from the server.
func websocketDidDisconnect(socket: WebSocket, error: NSError?) {
println("websocket is disconnected: \(error!.localizedDescription)")
}
// websocketDidReceiveMessage is called when the client gets a text frame from the connection.
func websocketDidReceiveMessage(socket: WebSocket, text: String) {
println("got some text: \(text)")
}
// websocketDidReceiveData is called when the client gets a binary frame from the connection.
func websocketDidReceiveData(socket: WebSocket, data: NSData) {
println("got some data: \(data.length)")
}
Next create the websocket client with the url and the port of your back-end. Append the following code to the viewDidLoad
function:
let socket = WebSocket(url: NSURL(scheme: "ws", host: "labs.denbeke.be:1234", path: "/")!)
socket.delegate = self
socket.connect()
At this stage we have everything in place to actually handle the incoming WebSocket messages, which are marshalled in the following JSON format:
{
"Content":"But even cooler, a native iPhone app written in Swift!",
"Time":1433942629
}
To do this you need to implement the websocketDidReceiveMessage
function:
func websocketDidReceiveMessage(ws: WebSocket, text: String) {
let json: NSDictionary
do {
// Parse JSON request
try json = NSJSONSerialization.JSONObjectWithData(text.dataUsingEncoding(NSUTF8StringEncoding)!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
// Do something with the parsed JSON data
if (json["Content"] != nil) {
textView.text = (json["Content"] as! String) + "\n\n" + textView.text
}
}
catch {
print("Could not parse json message")
}
print("Received text: \(text)")
}
The above snippet will try to parse the JSON request into a Swift dictionary/map. If this goes without any error, the content of the message will be prepended in the TextView we created earlier.
All together your ViewController should look like this:
import UIKit
import Starscream
class ViewController: UIViewController, WebSocketDelegate {
@IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// padding of TextView: top, left, bottom, right
textView.textContainerInset = UIEdgeInsetsMake(8,8,4,8);
// Setup WebSocket
let socket = WebSocket(url: NSURL(scheme: "ws", host: "denbeke.be:1234", path: "/")!)
socket.delegate = self
socket.connect()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Interface for WebSockets
func websocketDidConnect(ws: WebSocket) {
print("websocket is connected")
}
func websocketDidDisconnect(ws: WebSocket, error: NSError?) {
if let e = error {
print("websocket is disconnected: \(e.localizedDescription)")
}
}
func websocketDidReceiveMessage(ws: WebSocket, text: String) {
let json: NSDictionary
do {
// Parse JSON request
try json = NSJSONSerialization.JSONObjectWithData(text.dataUsingEncoding(NSUTF8StringEncoding)!, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary
// Do something with the parsed JSON data
if (json["Content"] != nil) {
textView.text = (json["Content"] as! String) + "\n\n" + textView.text
}
}
catch {
print("Could not parse json message")
}
print("Received text: \(text)")
}
func websocketDidReceiveData(ws: WebSocket, data: NSData) {
print("Received data: \(data.length)")
}
}
The complete iPhone app should look like this:
I have uploaded the source code of the app to Github: Liveblog-iOS-App