If you've read the previous articles of this series, you should have a good grasp of the fundamentals of the Swift programming language by now. We talked about variables, constants, and functions, and in the previous article we covered the basics of object-oriented programming in Swift.
While playgrounds are a great tool to play with Swift and learn the language, it's time to move on and create our first Swift project in Xcode. In this article, we're going to create the foundation of a simple to-do application using Xcode and Swift. Along the way, we'll learn more about object-oriented programming and we'll also take a closer look at the integration between Swift and Objective-C.
Prerequisites
If you'd like to follow along with me, then make sure that you have Xcode 6.3 or higher installed on your machine. At the time of writing, Xcode 6.3 is in beta and available from Apple's iOS Dev Center to registered iOS developers.
The reason for requiring Xcode 6.3 or higher is to be able to take advantage of Swift 1.2, which Apple introduced a few days ago. Swift 1.2 introduces a number of great additions that we'll take advantage of in the rest of this series.
1. Project Setup
Step 1: Choose a Template
Launch Xcode 6.3 or higher and select New > Project... from the File menu. From the list of iOS Application templates, choose the Single View Application template and click Next.
Step 2: Configure the Project
Name the project ToDo and set Language to Swift. Make sure that Devices is set to iPhone and the checkbox labeled Use Core Data is unchecked. Click Next to continue.
Tell Xcode where you'd like to store your project and click Create in the bottom right to create your first Swift project.
2. Project Anatomy
Before we continue our journey into Swift, let's take a moment to see what Xcode has created for us. If you're new to Xcode and iOS development, then most of this will be new to you. By working with a Swift project, however, you will get a better feeling of how classes and structures look like and behave in Swift.
The project template doesn't differ much from a project created with Objective-C as the programming language. The most important differences are related to the AppDelegate
and ViewController
classes.
If you've worked with Objective-C in the past, then you'll notice that the project template contains less files. There are no header (.h) or implementation (.m) files to be found in our project. In Swift, classes have no separate header file in which the interface is declared. Instead, a class is declared and implemented in a single .swift file.
Our project currently contains two Swift files, one for the AppDelegate
class, AppDelegate.swift, and another one for the ViewController
class, ViewController.swift. The project also includes a storyboard, Main.storyboard, and a XIB file for the launch screen, LaunchScreen.xib. We'll work with the storyboard a bit later in this article. It currently only contains the scene for the ViewController
class.
There are a number of other files and folders included in the project, but we're going to ignore those for now. They play no important role in the scope of this article.
3. Inheritance
The first thing we'll touch upon in this article is inheritance, a common paradigm in object-oriented programming. In Swift, only classes can inherit from another class. In other words, structures and enumerations do not support inheritance. This is one of the key differences between classes and structures.
Open ViewController.swift to see inheritance in action. The interface and implementation of the ViewController
class is pretty bare-bones, which will make it easier for us to focus on the essentials.
UIKit
At the top of ViewController.swift, you should see an import statement for the UIKit framework. Remember that the UIKit framework provides the infrastructure for creating a functional iOS application. The import statement at the top makes this infrastructure available to us in ViewController.swift.
import UIKit
Superclass
Below the import statement, we define a new class named ViewController. The colon after the class name doesn't translate to is of type as we saw earlier in this series. Instead, the class after the colon is the superclass of the ViewController
class. In other words, the following snippet could be read as we define a class named ViewController
that inherits from UIViewController
.
class ViewController: UIViewController { }
This also explains the presence of the import statement at the top of ViewController.swift since the UIViewController
class is defined in the UIKit framework.
Overrides
The ViewController
class currently includes two methods, but notice that each method is prefixed with the override
keyword. This indicates that the methods are defined in the class's superclass—or higher up the inheritance tree—and are overridden by the ViewController
class.
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } }
The override
construct doesn't exist in Objective-C. In Objective-C, you implement an overridden method in a subclass without explicitly indicating that it overrides a method higher up the inheritance tree. The Objective-C runtime takes care of the rest.
The same is true in Swift, but the override
keywords adds safety to method overriding. Because we've prefixed the viewDidLoad
method with the override
keyword, Swift expects this method in the class's superclass or higher up the inheritance tree. Simply put, if you override a method that doesn't exist in the inheritance tree, Swift will throw an error. You can test this by misspelling the viewDidLoad
method as shown below.
4. User Interface
Declaring Outlets
Let's add a table view to the view controller to display a list of to-do items. Before we do that, we need to create an outlet for the table view. An outlet is nothing more than a property that is visible to and can be set in Interface Builder. To declare an outlet in the ViewController
class, we prefix the property, a variable, with the @IBOutlet
attribute.
class ViewController: UIViewController { @IBOutlet var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } }
Note that the outlet is an implicitly unwrapped optional. A what? Let me start by saying that an outlet always needs to be an optional type. The reason is simple. Every property of the ViewController
class needs to have a value after initialization. An outlet, however, is only connected to the user interface at runtime after the ViewController
instance has been initialized hence the optional type.
Wait a minute. The tableView
outlet is declared as an implicitly unwrapped optional, not an optional. No problem. We can declare the tableView
outlet as an optional by replacing the exclamation mark in the above snippet with a question mark. That would compile just fine. However, this would also mean that we would need to explicitly unwrap the property every time we want to access the value stored in the optional. This will quickly become cumbersome and verbose.
Instead, we declare the tableView
outlet as an implicitly unwrapped optional, which means that we don't need to explicitly unwrap the optional if we need to access its value. In short, an implicitly unwrapped optional is an optional, but we can access the value stored in the optional like a regular variable. The important thing to keep in mind is that your application will crash if you try to access its value if no value has been set. That's the gotcha. If the outlet is properly connected, however, we can be sure that the outlet is set when we first try to access it.
Connecting Outlets
With the outlet declared, it's time to connect it in Interface Builder. Open Main.storyboard, and select the view controller. Choose Embed In > Navigation Controller from the Editor menu. This will set the view controller as the root view controller of a navigation controller. Don't worry about this for now.
Drag a UITableView
instance from the Object Library to the view controller's view, and add the necessary layout constraints. With the table view selected, open the Connections Inspector and set the table view's dataSource
and delegate
outlets to the view controller.
With the Connections Inspector still open, select the view controller and connect the tableView
outlet to the table view we just added. This will connect the tableView
outlet of the ViewController
class to the table view.
5. Protocols
Before we can build and run the application, we need to implement the UITableViewDataSource
and UITableViewDelegate
protocols in the ViewController
class. This involves several things.
Step 1: Conforming to Protocols
We need to tell the compiler that the ViewController
class conforms to the UITableViewDataSource
and UITableViewDelegate
protocols. The syntax looks similar to that in Objective-C.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { ... }
The protocols the class conforms to are listed after the superclass, UIViewController
in the above example. If a class doesn't have a superclass, which isn't uncommon in Swift, then the protocols are listed immediately after the class name and colon.
Step 2: Implementing the UITableViewDataSource
Protocol
Because the UITableViewDelegate
protocol doesn't define required methods, we're only going to implement the UITableViewDataSource
protocol for now. Before we do, let's create a variable property to store the to-do items we're going to list in the table view.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { @IBOutlet var tableView: UITableView! var items: [String] = [] ... }
We declare a variable property items
of type [String]
and set an empty array, []
, as the initial value. This should look familiar by now. Next, let's implement the two required methods of the UITableViewDataSource
protocol.
The first required method, numberOfRowsInSection(_:)
, is easy. We simply return the number of items stored in the items
property.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.items.count }
The second required method, cellForRowAtIndexPath(_:)
, needs a bit more explaining. Using subscript syntax, we ask for the correct item from the array of to-do items.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { // Fetch Item let item = self.items[indexPath.row] // Dequeue Table View Cell let tableViewCell = tableView.dequeueReusableCellWithIdentifier("TableViewCell", forIndexPath: indexPath) as! UITableViewCell // Configure Table View Cell tableViewCell.textLabel?.text = item return tableViewCell }
We then ask the table view for a cell with identifier "TableViewCell"
, passing in the index path for the current row. Note that we store the cell in a constant named tableViewCell
. There's no need to declare tableViewCell
as a variable.
Another important detail is that we downcast the return value of dequeueReusableCellWithIdentifier(_:forIndexPath:)
to UITableViewCell
since it returns an object of type AnyObject
, which is equivalent to id
in Objective-C. To downcast AnyObject
to UITableViewCell
, we use the as
keyword.
We could use the as?
variant to downcast to an optional type, but since we can be sure that dequeueReusableCellWithIdentifier(_:forIndexPath:)
always returns a properly initialized instance, we can use the as!
keyword, implicitly unwrapping the result of the method call.
In the next line of code, we configure the table view cell, setting the text label's text with the value of item
. Note that in Swift the textLabel
property of UITableViewCell
is declared as an optional type hence the question mark. This line of code could be read as set the text label's text
property to item
if textLabel
is not equal to nil
. In other words, the text label's text
property is only set if textLabel
is not nil
. This is a very convenient safety construct in Swift known as optional chaining.
Step 3: Cell Reuse
There are two things we need to sort out before building the application. First, we need to tell the table view that it needs to use the UITableViewCell
class to create new table view cells. We do this by invoking registerClass(_:forCellReuseIdentifier:)
, passing in the UITableViewCell
class and the reuse identifier we used earlier, "TableViewCell"
. Update the viewDidLoad
method as shown below.
override func viewDidLoad() { super.viewDidLoad() // Register Class for Cell Reuse self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "TableViewCell") }
Step 4: Adding Items
We currently don't have any items to show in the table view. This is easily solved by populating the items
property with a few to-do items. There are several ways to accomplish this. I've chosen to populate the items
property in the viewDidLoad
method as shown below.
override func viewDidLoad() { super.viewDidLoad() // Populate Items self.items = ["Buy Milk", "Finish Tutorial", "Play Minecraft"] // Register Class for Cell Reuse self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "TableViewCell") }
6. Build and Run
It's time to take our application for a spin. Select Run from Xcode's Product menu or hit Command-R. If you've followed along and you're using Swift 1.2, you should end up with the following result.
Note that I've added a title, To Do, at the top of the view in the navigation bar. You can do the same by setting the title
property of the ViewController
instance in the viewDidLoad
method.
override func viewDidLoad() { super.viewDidLoad() // Set Title self.title = "To Do" // Populate Items self.items = ["Buy Milk", "Finish Tutorial", "Play Minecraft"] // Register Class for Cell Reuse self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "TableViewCell") }
Learn More in Our Swift Programming Course
If you're interested in taking your Swift education to the next level, you can take a look at our full course on Swift development.
Conclusion
Even though we've created a simple application, you've learned quite a few new things. We've explored inheritance and overriding methods. We also covered protocols and how to adopt the UITableViewDataSource
protocol in the ViewController
class. The most important thing you've learned, however, is how to interact with Objective-C APIs.
It's important to understand that the APIs of the iOS SDK are written in Objective-C. Swift was designed to be compatible with these APIs. Based on past failures, Apple understood that Swift needed to be able to hook into the iOS SDK without having to rewrite every single API in Swift.
Combining Objective-C and Swift is possible, but there are some caveats that we'll explore in more detail as we move forward. Because of Swift's relentless focus on safety, we need to take a few hurdles from time to time. However, none of these hurdles are too great to take as we'll find out in the next article in which we continue working on our to-do application.