Quantcast
Channel: AirPair Software Coding Tutorials & More
Viewing all articles
Browse latest Browse all 36

Swift Tutorial: Building an iOS application – Part 3

$
0
0
Jack is the author of RubyMotion for Rails Developers and the creator of the RubyMotion-focused MotionInMotion screencast. Ask Jack for help.

swift

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:

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!

Our finished Swift app

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 .


Viewing all articles
Browse latest Browse all 36

Trending Articles