Restaurant Helper: Part 1

Patrick Sanders Blog, Tutorial

Restaurant Helper: Part 1

running with changes

The idea:

This app is a collection of useful utilities you might need while at a restaurant; specifically tip calculator, bill splitter and tax calculator.

The app has three screens each of which shows a single utility. There is a tab bar at the bottom that lets you switch between tools.

In this part of the project, we're going to make a tip calculator. The user will be able to enter a bill amount and see how much the tip would be for different percentages.

Creating the Project

The first thing we need to do for this (and all) iOS apps, is to create an Xcode project. We're using Xcode 7.3.1 with Swift 2.2, if you're using an older or newer version of Xcode you might need to modify the code.

Open Xcode. You should see the following screen.

Xcode Start

We want to click "Create a new Xcode Project" on the left. Then we should see the following.

Xcode Create New Project

We want to make sure we're looking at templates for iOS applications (in the left panel). Then we want to click "Tabbed Application" Then click "Next" button.

Xcode Create New Project

In the options pane. We want to enter "Restaurant Helper" as the application name. You can enter whatever you like for "Organization name" and "Organization identifier".

For "Language" choose "Swift".

Then click "Next" button.

Xcode Create New Project choose file location

Choose a place to save you project and click "Create".

Our Empty Project

Once the project is created you should see the following screen.

Empty xcode project

Xcode is quite a complex utility and it can be overwhelming at first. Lets break the workspace down into some sections to make it easier to understand.

Template files

Using a template we are given a handful of files. We can use these files if we want, or delete them.

Empty xcode project

The biggest and the part of the workspace you'll use the most is the Editor. This is where you edit your app's interface, settings and code.

The Navigator is where you can switch between the projects in your files, search for things in your project, etc.

The Utilities area is where you will see information about files and objects in your project. If you haven't clicked anything else in the project, you should see information about your whole project there now (e.g. name, location of your project, etc).

The Toolbar is where you can run your app (left), see the status of your project (in the center), display and hide the Navigator change the layout of the Editor (right), and Utilities areas (far right).

Template files

AppDelegate.swift

The AppDelegate file is the heart of our app and is written in Swift. It is responsible for lifecycle events from the operating system (e.g. our app has been opened or closed) and then call other files in the project when these events occur. Every iOS project has one AppDelegate. We wont be changing anything in this file for this project.

FirstViewController.swift and SecondViewController.swift

View Controllers control what is displayed on the screen (e.g. buttons, images, videos, text, etc), as well as user interactions with things on the screen (e.g. tapping a button with your finger, shaking the device, rotating the device, etc). This project has two view controllers, one for each of the tabs that are included in the template. We'll need to add a third later.

Main.storyboard

A storyboard is a visual way to create and edit the interface of your app without writing any code. This separates our app into more easily managed parts, by separating logic and display. A single storyboard can link to many different View Controllers and make it easy to switch between them using segues. Storyboards may be the easiest, but are definitely not the only way to create interfaces (you can also make them with code or XIB files)

The Main.storyboard file will appear as soon as our app has finished launching. It is connected to the both view controllers and contains a Tab bar controller that orchestrates the switching between the tabs.

Assets.xcassets

This is a file that makes it simple to store and organize binary files (e.g. images, videos, icons, etc) in your project.

LaunchScreen.storyboard

This storyboard appears while your app is opening. We might not even see it for this project, because it is very simple and should launch quickly. We wont be making any changes to this file. LaunchScreens cannot be connected to a view controller code file.

Info.plist

plist stands for Property List* and is where essential configuration information about our project is stored. We wont be making any changes to this file for this project.

Products folder

This group shows any of our project targets (e.g. the built app) you should see a file called Magic8Ball.app if you expand it. Clicking it wont open anything in the Editor.

Adding assets.

First we're going add some other files to our project (an app icon and some other icons)

Our project includes an Assets.xcassets file, this is the preferred location to store images, icon, and other binary files.

Click Assets.xcassets once. In the editor you should see the following.

Assets catalog

Select AppIcon, first and second and delete them.

Download the assets for this app. Then open the folder.

Drag assets

Drag the contents of the folder into the left column.

Setting up the interface

We're going to start by designing our app's interface. To do this we're going to use Interface Builder. This is a visual editor for interfaces in Xcode. It can edit two types of files: Storyboards and XIB/NIB files.

Our project is going to be built using the included Main.storyboard file.

Click it once to open it. You should see the following.

Storyboard

If needed you can zoom in and out by control-clicking on the whitespace around the view controllers (the squares in the screen) This can make it easier to work with the files.

zoom

To start we're going to look at the first view controller. This one contains the text "First View".

First we want to change the name and icon of the view controller to show what it does.

icon and title

At the bottom of the view controller (the square), you should see a gray square at the bottom of the screen with the word "First" under it. Click the square so it looks selected (as above).

In the Utilities pane (right side) click the Attributes Inspector, shown below.

attributes inspector

Modify the title and image to match the following image. (Title as "Tip Calculator" and Image as "percent")

attributes

Next we want to delete the contents of the current view controller, as they aren't useful to us. Select "First View" and "Loaded by FirstViewController" and then click "delete" on your keyboard.

default view controller content

Next we want to add a title for our tip calculator. To do this we want to select a label from the Object Library.

The object library is in the bottom portion of the Utilities pane. It's logo is a circle with a square in it, as seen below.

object library

There are quite a few objects to choose from, so we need to find a Label, to do this we're going to filter the available objects with the search bar at the bottom.

object filter

Click where it says Filter and replace it with the word "Label". You should see only one result as seen above.

We now need to drag the label onto the screen. For this label we're going to drag it to the top left corner until we see the blue guides appear at the top and left. Then we want to drag the right handle of our label to the right side. See animation below.

drag label and resize

Note: Making the label the full width of the screen is not necessary, but I've found that it often prevents auto layout errors that some students have.

Next we need to set constraints on our label. This will make it so our interface will adjust to the size of the device regardless of screen size.

To set constraints we need to find the Auto layout buttons at the lower right corner of the Editor. They look like this.

autolayout buttons

With the label selected click the third button from the left. It's called Pin

autolayout constraints

At the top we see a square with four text boxes around it. This indicates how far an object should be to its nearest neighbor on that side. For our label, we want it to adjust to the width of the screen, to do this we pin it to both the left and right side of the screen. We also want it to always be at the top of the screen, so we pin it to the top.

To do this we want to click the "Tie fighter" shaped symbols between the text field and the square. They should turn red when they are selected.

When you've selected the three indicated above. Click Add 3 Constraints at the bottom of the panel.

Next we want to update the text in our label and the size/style.

Select the label and then look for the Attributes inspector in the Utilities pane (on the right of the screen)

First we want to change the text that says "Label" to "Tip Calculator"

update text

Then we want to change the size and style of the text. We do this by clicking the gray arrow in the Font section of the same pane.

Select a larger font size, change the style if you like then click done.

Then we want to select align the text to the center.

align center

You may see that your label is partially cut off now, and has an orange border around it.

constraint mismatch

This means that there is a difference between what you're seeing on the screen currently, and the constraints and attributes you've set (in this instance the text is currently being cut off, but it wont be if we ran the app.)

To get rid of this warning we need to update the frame of our label. First we need select our label, then we need to find the Resolve Autolayout Issues button in Autolayout buttons group.

fix autolayout issues

Then we need to click "Update Frames" (the one at the top)

update frames

Our label should expand to show the label in its entirety and the orange border should disappear.

Bill total

Next we're going to add a way to input the total of the bill into our app. To do this we want two things: a text field to input the value and a label to tell the user what they should put into the field.

First we want to drag in a label in from the object library. Put it below the title and on the left side of the screen (ideally we'll align it to the blue margins that appear).

Rename the label to "Bill total".

Next we need to add constraints. For this label we want it to always appear on the left side under the title. So we'll pin it to the left and top.

add constraints

Click Add 2 Constraints.

mismatched constraints

If you see an orange border around your label, we need to update frame again, by clicking Resolve Autolayout Issues and then Update Frames.

update frames

Hopefully, your label looks like this.

label

How the user knows what to input and we need to create a way for them to input it. For this we're going to use a UITextField. In the objects library enter Text Field and you should see the following object appear.

text field in object library

We want to drag in the Text Field so it is to the right of the Bill Total Label. Then we want to increase the width so the right side of the text field is almost at the right edge.

We want our text field to always be next to our label, under our title, and we want it to adjust size depending on the device so that it will take up as much space as possible. To do this we want to pin the text field to left (to the label), to the top (to the title) and to the right (to the right edge of the screen).

Then click Add 2 Constraints.

constraints

When we're finished we should have something that looks like this.

Because this is a text field that should only take numeric values (i.e. bill prices), we can show our users the number pad instead of the alpha-numeric keyboard that they get by default. This will make life easier for our users, but we shouldn't expect this to clean or validate our input, because there are ways to circumvent the displayed keyboard (e.g. using a physical keyboard, pasting a value from somewhere else)

To change the keyboard, select the text field. Open the attributes inspector on the right hand side.

attributes inspector

Then scroll down until you see the following drop downs and choose Decimal Pad for Keyboard Type. There is also a "Number Pad" option, but it doesn't show a full stop/period so it isn't what we want for our app.

finished text field

decimal pad

Tip percent

The next thing we want to let the user be able to enter a tip percentage. We could do this with another text field, but that means they would mean they would have to enter another number. We can do better than this. Since our numbers will be within a fairly small range 0 to 50 percent, we can create a slider, then the user can easily change the percentage.

To do this we actually want three things:

  1. A label to tell the user what the slider does
  2. A slider
  3. A label to tell the user what the slider's current value

First we want to a label on the left to tell the user what the slider is.

  • Drag a label into the view controller under Bill total label on the left
  • Pin it to the top (to the Bill total label) and to the left (to the left side of the screen)
  • Change the text to Tip Percent

Next we need a label on the right to show what value the slider will be set to.

  • Drag a label into the view controller under bill total text field on the right
  • Pin it to the top (to the Bill total text field) and to the right (to the right side of the screen)
  • Change the text to 20% (This will be our initial value)

It should look like this when we're done.

two labels

Next we want to add the slider.

In the object library, search for Slider

slider

Then we'll want to drag the label between the two labels. We will want to expand the width so that it fills almost all the space between the labels (Look for the guides to appear)

slider

Now we need to add constraints. We want the slider to always fill the space between our labels regardless of the screen size of the device. So we want to pin it left (to the Percent Tip label), to the right (to the 20% label) and to the top (to the Bill Total text field)

slider constraints

Once you've selected all three, click Add 3 Constraints

Next we want to set the minimum, maximum and default value of the slider itself. To do this, select the label and look for the following in the Attributes inspector

slider settings

Set the minimum value to 0, the maximum value to 35 (or higher if you're a big tipper) and the default value to 20.

Rounding the tip

Next we want to add another label below the Percent tip label and pin it to the left and top. Call it "Round to nearest dollar"

Then we're going to add a UISwitch.

switch in object library

Drag the switch onto the screen next to the label.

switch in view controller

Pin the switch to the left and top.

Next we want to set the default state of the switch. To do this we want to select the switch and look for State in the Attributes inspector (in the Utilities pane on the right side of Xcode)

switch state

We want the default state to be Off

Showing the tip on the screen

Possibly the most important part of the whole interface is the label that shows the tip amount.

Drag a new label to the view below our switch.

tip label

First we want to pin our label so it stays below the switch.

tip pin y-position

We also want to use align to center the label.

tip align center

Update the frame.

In the Attributes Inspector increase the font size to 38. And replace the word Label in Text with a single space (e.g. " ")

tip text style

For the moment we're finished creating our interface and we can move on to the exciting part.

Connecting Interface and Code

In order for our app to respond to changes in the interface, we need to connect our interface elements to our code file.

To do this we need to open the Assistant Editor

Show assistant editor

You should see the editor split into two parts with a code file appearing on the right side.

assistant mode

Before we begin, Xcode has given us some default code that we don't need. To get rid of some clutter we're going to delete it.

We want to delete everything from the first override to the second to last }

delete this

Now we want to create what Apple calls Interface Builder Outlets or IBOutlets for short. An IBOutlet is a special variable that your code files can use to get input from a storyboard or change the value of something in a storyboard.

To connect the an element in the storyboard to the code file, we Control click and drag the element from the storyboard to the code file. A popup menu should appear once you've released the object in the code file.

Lets start by control-dragging the Bill total text field to our code file.

drag the label

We want to enter billTotalTextField as the name of our outlet. Then click connect.

bill total outlet settings

Next we want to create an outlet for our slider.

slider outlet

We want to enter tipPercentSlider as the name of our o9utlet. Then click connect.

slider settings

Next we want to create an outlet for our switch.

switch outlet

We want to label our switch roundingSlider (bad name, sorry, it's a switch not a slider)

switch outlet settings

We have added outlets for our three input objects, now we need to create outlets for our output objects. For this app we're going to have two (the tip percent display, and the tip total display)

tip label outlet

Set the outlets name to percentLabel

tip label outlet settings

The tip output label is a bit trickier, because it contains only a space, which makes it difficult to select in the editor. Instead of selecting it in the view, we're going to select it in the document tree (the right part of the editor) and drag it to the code file from there.

tip price label

Set the outlets name to tipPriceLabel

tip price label name

Creating and triggering an action

IBOutlets let our code file access values in our storyboard, but unless we notify the storyboard of a change to our interface, we'll have no way of knowing we need to update anything.

To notify the code file that we've updated a value, we want to create a special type of function called an Interface Builder Action (IBAction for short).

To do this we drag the element in the storyboard that we want trigger our action (in our case, changes to the text field, slider and switch) to the code file just like we did to create an outlet. Select the text field and control drag to an empty line in code file.

Create action

The important difference between the creating an action and an outlet, it that you have to switch select "Action" instead of Outlet in the options popup.

options action

Then we want to enter the name updateTip, select AnyObject for Type (this is important), Editing Changed for Event, and None for arguments.

Click Connect

You should see the following appear in your code file.

@IBAction func updateTip() {
}

This is a function that is connected to our storyboard. A function is a reusable block of code that performs some logic.

This is great, whenever we change the bill total, this function will be called, and we can update the tip amount, however, we also want our tip amount to update if we change the tip percent or select that we want only a . In order to have it do that, we just need to connect the slider and switch to the same function.

To do that we control-drag our slider and out switch (separately) to our existing action until a blue box appears around it. Then let go.

connect slider and switch

Now they're connected.

Now we can switch back to single editor mode because we've made all of our connections, and it's time to move on to coding.

editor modes

Adding the logic

Now we need to switch from our storyboard file to our FirstViewController.swift file.

first view controller

It should contain roughly this:

import UIKit
 
class FirstViewController: UIViewController {
 
    @IBOutlet weak var billTotalTextField: UITextField!
    @IBOutlet weak var tipPercentSlider: UISlider!
    @IBOutlet weak var roundingSlider: UISwitch!
 
    @IBOutlet weak var percentLabel: UILabel!
    @IBOutlet weak var tipPriceLabel: UILabel!
 
    @IBAction func updateTip() {
    }
}

The code we're going to add today will be within our IBAction, so lets add a few empty lines so we have space to enter some stuff.

@IBAction func updateTip() {
 
 
}

Conceptually our steps for our logic will be this:

  • get tip percentage from the slider
  • turn tip percentage into a whole number (integer)
  • update the tip percent label
  • get the bill total from the bill total text field
  • check that the bill total can be turned into a number (e.g. it doesn't contain letters or emojis)
  • calculate the tip amount
  • check whether the switch is on or off
  • round our number if the switch is on
  • set the tip total to the label in the interface

Let's get started.

Getting the tip percentage and converting it to a whole number

To get the value of a slider we type outletName.value. In our case our outlet's name is tipPercentSlider so we need to add tipPercentSlider.value to our function.

We're going to convert it to an integer (a whole number) because we don't need the fractional part of a percent. To do this we wrap the above code with Int().

Finally we want to set this to a constant so we can use it later. Together this would be:

@IBAction func updateTip() {
   let tipPercent = Int(tipPercentSlider.value)
}

Next we need to set the percentLabel's text to being our percent with a percent sign. To do this we have to inject our integer into a string. Putting variables in a string literal requires a special escape sequence \(variableName)

@IBAction func updateTip() {
   let tipPercent = Int(tipPercentSlider.value)
   percentLabel.text = "\(tipPercent)%"
}

Getting the bill total and checking that it's a number

Next we need to get the bill total form the text field. To get the value from our outlet by typing billTotalTextField.text. Then we need to convert it to a double by wrapping it in Double(). Then we want to assign it to a variable that we can use later to do some math.

Casting a string (e.g. text) to a Double (e.g. a number with a decimal point) is an operation that can fail (you can't convert '10.a7d' to a number). If the operation fails, we don't want to calculate the tip total (as we don't have a valid bill price).

To ensure our bill total can be converted to a number and stop our logic if it can't we're going to use a guard statement. A guard statement will check if a condition is true, and if not it will run an "else" block. Our guard statement will call 'return' if it fails. Return will stop execution of our updateTip function.

All together that will look like this:

@IBAction func updateTip() {
   let tipPercent = Int(tipPercentSlider.value)
   percentLabel.text = "\(tipPercent)%"
 
   guard let billTotal = Double(billTotalTextField.text!)
      else { return }
}

After we've done that we need to calculate the tip total.

To do this we multiply our billTotal by our tipPercent variable. To do this they need to be of the same type, so we want to convert our tipPercent variable, which is an integer to a double by wrapping it in Double(). Then we divide by 100. Finally we create a variable called tipTotal and assign our tip total to that.

@IBAction func updateTip() {
   let tipPercent = Int(tipPercentSlider.value)
   percentLabel.text = "\(tipPercent)%"
 
   guard let billTotal = Double(billTotalTextField.text!)
      else { return }
 
   var tipTotal = billTotal * Double(tipPercent) / 100
}

Next we want to check if our rounding switch is on. If it is, we want to round our number up to the next whole value. If not, we aren't going to do anything.

The first step to do this is to get the value of our switch. To do this we call the switch's on property. This will give us the value of True or False (i.e on or off)

For this switch we would call roundingSlider.on.

Once we have the value we use an if statement (a Swift control flow construct that lets us perform conditional operator)

Inside the if statement (between the curly braces {}), we put the logic we want to be performed if the switch is on. In this case we want to round the tip up to the next even dollar. We use the ceil function for this. (if you want to round down you could use floor instead, you cheap…)

@IBAction func updateTip() {
   let tipPercent = Int(tipPercentSlider.value)
   percentLabel.text = "\(tipPercent)%"
 
   guard let billTotal = Double(billTotalTextField.text!)
      else { return }
 
   var tipTotal = billTotal * Double(tipPercent) / 100
 
     if roundingSlider.on {
            tipTotal = ceil(tipTotal)
   }
}

Finally we need to want to put the total (rounded or not) into the tip total label. However, we probably want to format it first (i.e. we want a $ at the beginning, and only two decimal places)

To format the number into a string (text) we want to create a string with the format initializer.

The basic syntax for formatting and inserting a float into a string is this String(format: "%f", floatVariableName)

The key here is the %f which inserts our float into the string.

Lets say we have the float 10.987234. Using the above code we would get the output "10.987234". We don't want so many decimal places, so we can tell %f that we want less by putting .2 between % and f, which means, "I want two decimal places only please." Then we have "10.98", because this truncates but doesn't round.

Then we want to add a dollar sign at the beginning, to do this we just add a $ sign in front of the %. String(format: "$%.2f", floatVariableName)

Then we need to insert it into our tipPriceLabel by using the text property.

@IBAction func updateTip() {
   let tipPercent = Int(tipPercentSlider.value)
   percentLabel.text = "\(tipPercent)%"
 
   guard let billTotal = Double(billTotalTextField.text!)
      else { return }
 
   var tipTotal = billTotal * Double(tipPercent) / 100
 
     if roundingSlider.on {
            tipTotal = ceil(tipTotal)
   }
 
     tipPriceLabel.text = String(format: "$%.2f", tipTotal)
}

Now if we run our app we should see the following.

running

If we add a bill price, make changes to the slider, or the switch we should see the tip update immediately.

running with changes