Table of Contents
1 Introduction
I set a challenge for you at the end of part 2: take a shot at implementing the save feature, but not the task display on your own. I hope you did well! As promised, here’s the code it took to get that working:
if segue.identifier == "dismissAndSave" {
let task = Task(title: titleField.text, notes: notesField.text)
TaskStore.sharedInstance.add(task)
}
You also need to make sure you’ve got the correct identifier set for all of the segues in your Storyboard.
Well…that was straight into it, wasn’t it? Lets take a step back for a second and refresh our memories on what we’ve been up to until now.
If you’ve been following along with the series — here are part 1 and part 2 — you’ll have learnt a large amount about the difference Swift makes our use of the Apple frameworks and how much cleaner our code can be.
We’ve also tackled our first feature, adding a task, which we left nearly complete at the end of part 2.
If you’re just joining us here in part 3, I highly recommend heading back to parts 1 and 2 to catch up, because we’re building on a lot of knowledge introduced earlier.
2 What you need
We’re going to be building an iOS app in Swift throughout this series, but to do so, you’ll need four things:
- A Mac with either OS X Mavericks or the latest OS X Yosemite beta release.
- A working, installed copy of Xcode 6 Beta 4 or higher.
- A basic understanding of Swift, which you can get from our Swift Reference Guide.
- To have read part 1 of this series.
This Swift tutorial was written with beta 5 as the current release, and given how active the development is on Swift at the moment, you might read this before we’ve had a chance to update it. So if a code example isn’t working, double check the changes to the language since beta 5.
3 Finishing off Save
Time to finish off where we left our app. Open up your Main.storyboard file and make sure the two segues between the buttons from our AddTaskViewController
and MasterViewController
have good names, such as “dismissAndCancel” and “dismissAndSave”. This will make it easy for us to identify them in this next bit.
In your AddTaskViewController.swift file, we’re going to set up our prepareForSegue:sender:
method.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if segue.identifier == "dismissAndSave" {
let task = Task(title: titleField.text, notes: notesField.text)
TaskStore.sharedInstance.add(task)
}
}
First of all, we’re going to check that the segue we’re preparing for is the save segue, because our cancel segue will do everything we want it to do anyway, which is nothing.
It’s good practice, even when it’s the only segue you have set up, to be explicit about which segue you want the code to run for. That way when things change later, the developer — which could be you — will know exactly what is going on and how pieces are connected.
If this is the segue we’re looking for (cue Star Wars joke), we’re going to assign a new task to a constant, filling out that task from the two text fields.
Then we can use the TaskStore
that we created in part 2 to keep our Task
safe and sound by adding it to the sharedInstance
.
Running the application now, everything “works” up until we get to the list. At least there aren’t (or shouldn’t be) any crashes, and things are running smoothly.
There is no point in saving a task if we can’t actually see it, though, so let’s start getting into our second feature.
4 Listing Tasks
Before we start trying to list tasks, it’s important to mention that as of right now, no data is being stored between launches.
I suggest that you learn a little about CoreData after you’ve completed this tutorial series, or maybe even the new and shiny Realm, which is aiming to be the easy replacement for CoreData and sure has turned some heads.
Side note: we’ve been incredibly spoiled this past few months for new tools! It’s a good time to be joining the community!
4.1 Using what we’ve got
Before we start customising things too much, lets see how far we can get using what we have already.
When we generated the project, we were given a MasterViewController
with the basic delegate and data source methods set up for us, including deletion.
We’ve also got an extremely basic DetailViewController
, complete with that absolutely gorgeous label in the middle of the screen (sarcasm of course).
We’ll worry about the design in a bit “when the designers have it ready” (there are no designers, the designers are a lie). For now, we just want to focus on being great programmers who live on the edge and to get something working in Swift.
So let’s do that. We’ll start by opening up our MasterViewController.swift file and making a few changes, the first of which will be removing the unwanted objects
property on our MasterViewController
. Delete the line at the top of the controller that looks like this:
var objects = NSMutableArray()
We have no need for that anymore. Removing it is going to make Xcode mad though, because that property is used in quite a few places. Lets start fixing the errors from top to bottom, replacing the old code with the new code that uses our TaskStore
.
4.2 Opening the detail screen
The first place that an error shows up is in the prepareForSegue:sender
method of our MasterViewController
. We need to make two changes to the last two lines inside the if statement:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
let indexPath = self.tableView.indexPathForSelectedRow()
let task = TaskStore.sharedInstance.get(indexPath.row)
(segue.destinationViewController as DetailViewController).detailItem = task
}
}
On the second line inside the if
statement, we change the constant name to task
instead of object
, and use the get
method on the sharedInstance
of our TaskStore
to get the task that we’re after. No need for casting now either, because we know exactly what we’re getting back from the get
method: a Task
.
The change for the third line inside of the if
statement is comparatively boring; all we’re doing is setting the detailItem
of our DetailViewController
to be the task
instead of the object
because we changed the constant name.
We now need to fix a problem elsewhere, specifically in our DetailViewController
, so open up the DetailViewController.swift file and we’ll make a small change.
4.3 No more AnyObject, we like structs
The DetailViewController
is expecting an AnyObject?
, which means a struct just isn’t going to fit into place here. We can fix that though by changing the type of the detailItem
property to be Task?
instead.
var detailItem: Task? {
didSet {
// Update the view.
self.configureView()
}
}
While we’re here, we may as well fix up the rest of this controller. It won’t take much, it’s just the configureView
method that we need to change.
Where we unwrap the detailItem
optional, we’re saying it should be of the type AnyObject
, which we know isn’t true because we just changed away from that, so let’s fix it. Then, because Task
doesn’t have a description
property, we’ll change it to be title
:
func configureView() {
// Update the user interface for the detail item.
if let detail: Task = self.detailItem {
if let label = self.detailDescriptionLabel {
label.text = detail.title
}
}
}
That’s all we’re going to change in our DetailViewController
until some design changes come in, so we can head back to our MasterViewController.swift file and finish that off.
4.4 Changing the data source and delegate
The next error in line for fixing in our MasterViewController
is the UITableViewDataSource
protocol method tableView:numberOfRowsInSection:
.
At the moment we’re just calling count
on what was the array of objects. Now we want to call count
on our sharedInstance
of our TaskStore
instead.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return TaskStore.sharedInstance.count
}
Of course we don’t actually have a count
property at the moment, so we’ll create that now. Open up the TaskStore.swift file and add this under the tasks
property declaration:
var count: Int {
get {
return tasks.count
}
}
We could also have created this as a method, like so:
func count() -> Int {
return tasks.count
}
It would have been shorter, but it lacks some style. By making count
a computed property, we keep a consistent API with Array
and don’t try to force what is essentially a property into a method.
What you might not know, though, is that we can actually make this even more concise than the method definition. If we’re creating a computed property, it has no setter, and so we can actually get rid of the second and fourth lines and have this instead:
var count: Int {
return tasks.count
}
Now, isn’t that nice and expressive? It’s obviously a property, and it’s obvious that it’s a computed one and what it’s computing. Yay for Swift!
4.5 Back to the data source
Moving on from the distraction of the small niceties in Swift, let’s finish off this data source. Our tableView:numberOfRowsInSection:
method is fine now, so it’s time to move onto the tableView:cellForRowAtIndexPath:
method.
This change is very similar to the change in our prepareForSegue:sender:
method in our MasterViewController
and the configureView
method of our DetailViewController
, we’re just changing how we get our Task
and which property we’re using from it.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let task = TaskStore.sharedInstance.get(indexPath.row)
cell.textLabel.text = task.title
return cell
}
The two lines that changed were the two just before the return
statement. Instead of using the objects
array and its subscript operator, we’re going to use the get
method from our TaskStore
once again, assigning the result to a task
constant instead of a constant called object
.
On the last line before the return
statement, we change what we’re setting the text
property of our textLabel
to the title
of the task we got.
Only one thing left to change now, and that’s one of our delegate methods.
4.6 Finishing up the basics with the delegate
You can probably see that we have one error left, and that’s in the UITableViewDelegate
method tableView:commitEditingStyle:forRowAtIndexPath:
. The change here is quite simple, but it’s going to require that we add something to our TaskStore
again:
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
TaskStore.sharedInstance.removeTaskAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
We only had to alter one line in this method, changing objects.removeObjectAtIndex(indexPath.row)
to TaskStore.sharedInstance.removeTaskAtIndex(indexPath.row)
inside the .Delete
part of the if
statement.
Because this method doesn’t exist just yet, we need to create it. Open up your TaskStore.swift and we’ll add that in.
4.7 One more method for TaskStore
At the end of our TaskStore
class, we’re going to add in the simple removeTaskAtIndex:
method:
func removeTaskAtIndex(index: Int) {
tasks.removeAtIndex(index)
}
The reason we do this instead of just accessing the tasks
array directly and calling the removeAtIndex:
method on the array is that it allows us to define an API for consumers of our TaskStore
class. Later on, we can change the internals of our TaskStore
class without breaking a bunch of other code.
It’s always good to think about how the choices you make now might affect you in the future, but don’t go around optimising everything for every possible future case.
5 Prettying things up a bit
There are a few small changes we can make to the design that will enhance the application. We still haven’t received our “design from our designers,” but there is an obvious flaw in our design. How do we view the notes?
5.1 Altering the Storyboard
In your Main.storyboard file, click on the prototype table view cell. Now we need to change from the default UITableViewStyle — which has just one text label — to the subtitle style so we can show a preview of the notes.
With the table view selected, we can change the “Style” in the Attributes Inspector. In the drop down, select “Subtitle” and you should see the prototype cell change to contain a second label.
5.2 Updating our MasterViewController
Taking advantage of the new label, it’s time to make some changes to our MasterViewController
. Open up the MasterViewController.swift file and we’ll make some changes to the tableView:cellForRowAtIndexPath:
method.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let task = TaskStore.sharedInstance.get(indexPath.row)
cell.textLabel.text = task.title
cell.detailTextLabel.text = task.notes
return cell
}
The only change here is setting the text
property of our detailTextLabel
between where we’re setting the text
property of the textLabel
and the line where we return the cell from the method.
Now if we run the application and add a task (making sure that we fill out the notes field), we can see a preview of our notes when the list of tasks comes back up. Of course if you fill out the notes field with something too long to fit in the detailTextLabel
it will just cut it off. That’s what we’re looking to do.
5.3 Redesigning the detail view
The small centred label isn’t really going to cut it, so open the Main.storyboard file up again; we’re going make some changes to the DetailViewController
.
Ultimately it doesn’t matter what you make it look like as long as you have the two views I have. The first is the one already in the DetailViewController
, and I’m going move the label up to the top and make it a bit bigger — remember to update the constraints.
Next we need to drag out a Text View from our Object Library into our DetailViewController
, then make a few changes in the Attributes Inspector after sorting out the layout.
Let’s pump the font size up to 18 points using the font picker which you can access by clicking the “T” icon in the font field. Then, uncheck the “Editable” checkbox just underneath the font field. It would also be nice if we can open up links and phone numbers straight from the notes, so lets check “Links” and “Phone Numbers” in the “Detection” section of the Attributes Inspector.
Finally, we need to link up the Text View and the DetailViewController
. Open up the Assistant Editor (click the tuxedo looking icon in the toolbar), then hold control
and drag from the Text View to just underneath where we define the IBOutlet
for our detailTextLabel
and call the new outlet notesView
.
5.4 Displaying our notes
With an outlet created, all we have left is the simple task of putting the content into it. Open up the DetailViewController.swift file, and let’s make some changes to our configureView
method.
func configureView() {
// Update the user interface for the detail item.
if let detail: Task = detailItem {
if let label = detailDescriptionLabel {
label.text = detail.title
}
if let notes = notesView {
notes.text = detail.notes
}
}
}
You can see the changes here inside of the new if
statement inside of the wrapping if
statement. I’ve also gone through and removed the pointless use of self
in front of all the properties.
First we’re going to unwrap our optional UITextView
, even though it’s implicitly unwrapped. Then, if it exists, it’s going to be assigned to the notes
constant, so we can set the text of our notesView
to be the notes
property of our detail
item (a Task
).
5.5 A basic app
When you hit Run now, you’ll have a basic task management application to play with! Yay! Congratulations on building your first iOS application in Swift!

It’s not going to be the next big app, but it’s a great starting point for you to learn what it’s like to build an iOS application using Swift and Xcode 6. If you did the same with Objective-C, you would see a large amount of extra — and more importantly, less expressive — code.
This is the end of the series for now, but don’t let that stop you. There is plenty going on in the iOS community around Swift right now, continue searching and learning now that you’re on your path to becoming a Swift Master!
The post Swift Tutorial: Building an iOS application – Part 3 appeared first on .