Restaurant Helper: Part 1
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.
We want to click "Create a new Xcode Project" on the left. Then we should see the following.
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.
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.
Choose a place to save you project and click "Create".
Our Empty Project
Once the project is created you should see the following screen.
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.
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).
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.
Select AppIcon, first and second and delete them.
Download the assets for this app. Then open the folder.
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.
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.
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.
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.
Modify the title and image to match the following image. (Title as "Tip Calculator" and Image as "percent")
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.
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.
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.
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.
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.
With the label selected click the third button from the left. It's called Pin
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"
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.
You may see that your label is partially cut off now, and has an orange border around it.
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.
Then we need to click "Update Frames" (the one at the top)
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.
Click Add 2 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.
Hopefully, your label looks like this.
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.
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.
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.
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.
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:
- A label to tell the user what the slider does
- A slider
- 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.
Next we want to add the slider.
In the object library, search for 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)
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)
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
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.
Drag the switch onto the screen next to the label.
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)
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.
First we want to pin our label so it stays below the switch.
We also want to use align to center the label.
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. " ")
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
You should see the editor split into two parts with a code file appearing on the right side.
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 }
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.
We want to enter billTotalTextField as the name of our outlet. Then click connect.
Next we want to create an outlet for our slider.
We want to enter tipPercentSlider as the name of our o9utlet. Then click connect.
Next we want to create an outlet for our switch.
We want to label our switch roundingSlider (bad name, sorry, it's a switch not a slider)
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)
Set the outlets name to percentLabel
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.
Set the outlets name to tipPriceLabel
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.
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.
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.
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.
Adding the logic
Now we need to switch from our storyboard file to our FirstViewController.swift file.
It should contain roughly this:
import UIKitclass 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) / 100if 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) / 100if roundingSlider.on {tipTotal = ceil(tipTotal)}tipPriceLabel.text = String(format: "$%.2f", tipTotal)}
Now if we run our app we should see the following.
If we add a bill price, make changes to the slider, or the switch we should see the tip update immediately.