iOS
iOS development requires that you have (access to) a Mac running a recent version of macOS.
- If you have a Mac but it’s on the older side, select > About This Mac, and let heads@cs50.harvard.edu know which version of macOS you have.
- If you don’t have a Mac, you may use a Mac in the lab in Science Center B09B. But, before leaving the lab, be sure to save your work, as on Google Drive, on your own USB drive, or the like.
What to Do
- After watching Lessons 1, 2, and 3, submit Pokédex.
- After watching Lesson 4, submit Fiftygram.
- After watching Lesson 5, submit Notes.
- For Pokédex, Fiftygram, and Notes, record a screencast (about 2 minutes in length each) with voiceover in which you demonstrate your mobile apps and their features. Upload your videos to YouTube as “public” or “unlisted” and take note of their URLs.
- Submit this form.
When to Do It
By 11:59pm on Fri 11/22.
How to Do It
Introduction
- In this track, we’ll be covering how to build apps for Apple’s iOS platform, using the programming language Swift. We’ll learn by example from three apps, one that reads data from the internet using an API, one that implements image filters, and one that lets you take notes on your phone.
Lesson 1
- Xcode is the IDE made by Apple that we’ll use to write our apps, and it includes all the tools we’ll need, though it is only available for macOS.
- Newer iOS apps are generally written in a language called Swift, though some use an older language, Objective-C.
- [0:37] Swift is similar to C, but there are some differences.
- When declaring variables, we can set them to be constant (immutable) with
let
, or mutable withvar
. This will help protect us from accidentally changing values that should remain constant. - Data types include:
Array
Bool
Dictionary
Int
Double
,Float
Set
String
- We take a look at a few snippets of syntax for conditions, arrays, dictionaries, and string interpolation, or substituting variables in strings.
- [7:23] We see function declarations, like
func sayHello(name: String) -> String { ... }
, where we use thefunc
keyword to declare the function name, and then the parameter(s) and the return value. When we call the function, we also need to include the name of the parameter we’re specifying, withsayHello(name: "Tommy")
. - Structs also exist, acting as a container for data, with multiple fields of different types that we can specify.
- Classes are like structs, but can also have functions. For example, we might have a
class Person
that has a special function, or method, calledinit
, to create one instance of thePerson
class. The instances are commonly referred to as an object. - We can add another method to our class, like
sayHello()
, and after we create an instance of it withlet person = Person(name: "Tommy")
, we can call it withperson.sayHello()
. This time, our function doesn’t need a parameter forname
, since our instance can use thename
we passed in when we created it. - [13:34] With inheritance, we can create a subclass of a given class, which will by default include the methods of the class it inherits from. But we can override any of those methods to do something different with
override func
. - [16:01] Protocols, or interfaces in other languages, are like a list of methods that a class must implement. In a protocol, we don’t specify how the function is implemented, but just that it needs to exist on any class using it. This helps our compiler help us be sure that our classes can be used as we would expect.
- [17:53] Optionals, which we can declare with something like
var name: String?
, allow us to declare variables that might have the value of nil, the equivalent of a null value. To use this variable, we need to then check that it’s not nil, withif let n = name
(which we can think of asif (name != null)
). Then, inside the condition, we’ll know that we can usen
as a string. Again, this helps the compiler help us avoid mistakes. (Though, we can bypass that with using the variable asname!
, by adding an exclamation point.) - [21:14] We can also use a guard clause, with the syntax
guard let n = name else
, which sets the variablen
toname
ifname
is not nil, and continues outside of the condition. Ifname
is nil, then theelse
branch will run, perhaps returning with a special message. - [23:01] We can open Xcode, and click on “Create a new Xcode project”. We’ll go to the macOS tab, and choose the “Command Line Tool” template to get started with Swift. We’ll have a few details about our project to fill out, and we can use “CS50” as the Organization Name and “edu.harvard.cs50” as the Organization Identifier (the domain name backwards, by convention). (If we wanted to actually publish an app to the App Store, we’ll need to get our own account from Apple.) And we’ll be sure Swift is the selected language.
- The main screen will be our code editor, and the left side will show the files in our project. We can also change some settings on the right side.
- We can open the file
main.swift
, and see that there’s already some generated code for us. In the top left, we can compile and run our code with the triangular play button. In the bottom we have the output of our program, as though we ran it in the terminal. - [27:34] We can write an example program that sorts students into tracks, with a class, a constructor for the class called
init
, and an array of instances of the class. - Xcode has autocomplete, so as we type it’ll suggest types and highlight anything we might be doing incorrectly.
- We create an array of student names, and a dictionary where the keys are the names of the students (strings) and the values are tracks.
- Then, we iterate through our student array, pick a random track, and store it in our dictionary. Finally, we iterate over our dictionary and print out each student and their assignment.
Lesson 2
- To write our iOS app, we’ll be using a framework called UIKit, made by Apple, with lots of functions to create UI, or user interface, elements on the screen, like tables, images, and buttons.
- We’ll follow the MVC pattern, or model-view-controller, where we have models, or code that handles storing our data; views that handle displaying data; and controller that contains our logic for bridging our model and our view. In iOS, they’re called view controllers, and decide what gets shown when.
- [2:38] We take a look at table views, which shows a list of contents. Xcode allows us to have visual containers, or storyboards, with which we can create views and indicate how our app moves between them.
- IBOutler and IBAction types will be used for connecting data to views. Segues will allow us to map actions in the app to changes to our views or data.
- [4:35] We open Xcode again, and now we create a project with the “Single View App” template under the iOS tab. We don’t need Core Data, Unit Tests, or UI Tests yet, so we can leave them all unchecked.
- We’ll build a simple Pokédex app, which will show us a list of Pokémon and more details. We have some generated files:
AppDelegate.swift
, which we can think of as themain
function for our app, which will decide what happens when the app is started or closed, but we probably won’t need to override any of the default behaviors.ViewController.swift
, which will contain the code for what our app does when a view for our app is loaded.Main.storyboard
, which shows a preview of an iPhone screen and what our view will look like.Assets.xcassets
is a directory into which we can put assets like images and sounds.LaunchScreen.storyboard
, the view that our app will show while it starts.Info.plist
, a generated configuration file with everything we need for now.
- [12:25] We can start by opening our main storyboard, deleting the blank view controller we see, and adding a “Table View Controller” with the home button icon on the toolbar. We can drag it onto our storyboard, and we’ll also need to change our
ViewController.swift
file to match and connect to the controller. - We make sure that our view controller “Is Initial View Controller” in one of the panels on the right, and we can start by defining our model.
- We’ll create a new Swift file and call it
Pokemon.swift
. Inside, we’ll declare astruct Pokemon
with two fields, and inViewController
we can create an array of elements of thePokemon
type. - It turns out that we can override methods in our Table View, like
numberOfSections
,numberOfRowsInSection
, andcellForRowAt
to display our data in the table. - [20:50] In the storyboard view, we can find the tab for the Attributes inspector, and apply basic styles.
- Finally, in
cellForRowAt
we get a reusable cell, set the text label, and return it for the table to display. - [29:20] We can also add a title bar to show where we are, by using the storyboard to place our table view inside a Navigation Controller. Now, our table view can have a title.
- [31:30] We’ll add another view by searching for a regular View Controller and dragging it into our storyboard. We can add labels and use the UI to move and resize them.
- [34:20] We’ll now need to make another Swift file for this controller, and call it
PokemonViewController.swift
. We’ll use an IBOutlet in order to connect labels to variables in our class. First, in our storyboard we need to set the view controller’s class to our new class, and use the UI to set each label to the right outlet. - [38:20] We’ll have our table view pass in the
Pokemon
to thePokemonViewController
, and we can override theviewDidLoad
function to set the value of the labels based on the Pokemon passed in. - We’ll need to go back to the storyboard and create a Segue for our table view to transition to the new view controller. We’ll pick “Show” as the Segue type, and set an identifier we can use from our view controller.
- We’ll override
prepare(for segue)
and use theif let destination = segue.destination as? PokemonViewController
syntax to make sure that the view we’re going to is aPokemonViewController
. Then, we can get the right Pokemon for the row, and set it on the view directly. - [47:30] We can run our app and see the views as we expect now, and to format the number of the Pokemon we can use
String(format: "#%03d", pokemon.number)
.
Lesson 3
- We can use an API, application programming interface, to load data from the internet in our app. An API is like a set of code that someone else has written, designed for you to use too.
- In this case, we’ll be making requests to a website and getting data back in a format called JSON, JavaScript Object Notation.
- An object in JSON might look like a dictionary of key-value pairs:
{ "course": "cs50", "tracks": ["mobile", "web", "games"], "year": 2019, }
- The values can be a string, an array, or a number as we see here, or some other data types.
- [2:12] We’ll use a new pattern in Swift called try, catch, where any exception, or error, can be “caught”:
do { let result = try something() } catch let error { print (error) }
- In this case, the
something()
function might cause an exception, so we want totry
running it, andcatch
any error that does occur.
- In this case, the
- [3:15] A closure, or anonymous function, will also be helpful:
let reversed = names.sorted(by: {( s1: String, s2: String ) -> Bool in return s1 > s2 })
- We pass in a closure to the
sorted
function on thenames
array, which is a function that takes in two strings and returns a Boolean, acting as a comparison function between them.
- We pass in a closure to the
- [5:22] We’ll also see a pattern called delegates, where we can attach event handlers, so a change to one object can “notify” another one to respond; the logic to handle a change is “delegated” to somewhere else.
- [6:01] We’ll use https://pokeapi.co to get our data about Pokemon, in JSON format.
- In Xcode, we’ll populate our array of
pokemon
in theviewDidLoad
function of ourViewController
. - We can read the documentation of the API and figure out the URL format we need to use. Then, we can use the
URL
andURlSession
class in Swift to actually get the data. - [13:10] We’ll use a closure to handle the data (or error) we get back from our request, along with guards to make sure the types aren’t optional.
- [15:18] We’ll also need a struct to represent the data we get back, like
PokemonList
. With the JSONDecoder class, we can decode the data into a struct we define. To make our struct decodable, we also have to specify it is codable (convertable to and from JSON), so we usestruct PokemonList: Codable
in our definition. We also need to wrap this decoding function with a try, catch, since we’re not guaranteed that the data we get back will successfully be decoded. - [19:05] We try to run our app, and see an error about a key not found. It turns out the API doesn’t return a
number
field, that’s required by ourPokemon
struct. - [20:55] To display the data we get back, we can set our table’s data to the results of our decoded data, using the keyword
self
in a closure to be clear which variable we’re setting. - [22:50] And now that we’ve changed the data for the table, we have to tell the table to reload the view with
self.tableView.reloadData()
, but we actually have to ask the main thread (the foreground of the app) to reload the data, since our URL request is in a background thread. - [25:40] We can add a
capitalize
function to change the name of the Pokemon before we display it in our table. - [28:23] Let’s use the API in our second view, for each individual Pokemon. We can explore the API to see that a lot of information is returned. It turns out that one field,
types
, is an array with one or moretype
objects. We’ll create a struct to represent this data as a whole,PokemonData
, and add the fields we want to decode, withPokemonTypeEntry
andPokemonType
structs as needed. - [32:20] Now we can make a second API call in our
PokemonViewController
, changing the URL based on the Pokemon passed in, decoding the data, and setting the values of the labels in the view. We also need to be careful to clear placeholder data in our labels, since we might not have values to set them to each time.
Lesson 4
- We’ll make a different app this time, one that can apply filters to photos.
- We’ll create a new Single View App project in Xcode, and add some basic components to our main storyboard. We’ll add:
- a navigation controller so our app has a title
- a Bar Item that looks like a button in the navigation bar, labeled “Choose Photo”
- an Image View to show an image
- buttons for each of the filters that we’ll want
- [5:25] First, we want to be able to pick a photo and show it. We’ll need an
IBAction
to link to “Choose Photo”, anIBOutlet
for the Image View, and connect both using the storyboard UI in Xcode by clicking and dragging. - [7:35] To show the user’s photo gallery for them to select a photo, we’ll use the
UIImagePickerController
class and make sure that our class will be the delegate, or the class that will be responding to the user selecting a photo in that controller class. - [9:55] To display the image picker, we’ll need to use the Navigation Controller to
present
it, and use thedidFinish
function that the ImagePickerController will call for us. By looking through some documentation or examples, we’ll need to get aUIImage
from what’s passed in to the function, and set it on the Image View. We also need to dismiss the picker after a photo is selected. - [15:10] Now we can write some of the code to apply filters to the image after it’s been selected. We can look at the documentation for Core Image Filter from Apple, and notice that we have many built-in options like
CISepiaTone
. - [16:20] We’ll create another
IBAction
, connect it in the storyboard, and use theCIFilter
class to create an object we can use to filter our image. We need to convert the classes of images we have with a little extra code to guard the types. - [22:30] We can save the original image so the filter doesn’t keep getting reapplied, and be careful to add a guard to make sure it exists before we try applying a filter.
- [24:20] We’ll add a few more buttons for other filters, and create and connect
IBAction
s for them too. Since we’re now repeating some of this code, we’ll factor the common lines out into a helper method. Now, all three of our filters work.
Lesson 5
- SQLite is a simple SQL database where our Swift code can write queries and save data.
- We’ll need queries like:
CREATE TABLE
INSERT INTO
SELECT ... FROM
UPDATE ... SET
- [3:50] We’ll make a note-taking app as our example, and start by creating a Single View App in Xcode as before. We’ll want a Table View Controller to show a list of notes.
- [6:20] Now let’s define our model with a struct
Note
, and show a list of those in our table by overriding the functions on our ViewController. - [10:00] We’ll define a new class,
NoteManager
, to work with the database. With SQLite, we can save and open our database like a file, so we’ll use the built-inFileManager
to open or create a file callednotes.sqlite3
in a function calledconnect
. - [13:15] We need a variable of type
OpaquePointer
, which is just a pointer to our database file. This way,sqlite3_open
can keep the same connection to the database. - [15:40] We’ll also create our table if it doesn’t exists, as we open the database file.
- [17:20] To create a new note, we’ll have a
create
function that first tries toconnect
(in case we haven’t already) and thensqlite3_prepare_v2
to prepare a statement. We’ll then usesqlite3_step
to run the next step of the statement, andsqlite3_finalize
to finish executing it. - [22:00] We’ll have our
create
function also return the ID of the row we just created, in case we want to do something else with it right after. - [22:55] Next, we’ll write
getAllNotes
to return a list of theNote
s we have saved, by preparing aSELECT
statement. Since we want to run this query for each row we can get back, we’ll have to use awhile
loop to run each step of our statement until there are no more rows. Inside our loop, we’ll usesqlite3_column_int
orsqlite3_column_text
to get the values of each column from the last row from our statement, and build aresult
array ofNote
s to return. - [28:05] We’ll add a Bar Item button and link it to an action to create a new note. In our view controller, we’ll create a
NoteManager
. We’ll add amain
reference inNoteManager
to an instance of itself (a singleton, since there will ever be one of them that we need), and make sure thatinit
is marked asprivate
so we won’t have multipleNoteManager
s. - [31:15] Now, our action can create a new note in the database, but we’ll need a new method to reload data from the database and refresh our table view.
- [33:40] When we try to run our app, we couldn’t create the database, and it turns out we need to use
documentDirectory
as the directory for our database file. But after creating a note and stopping and starting our app again, we don’t see the note saved. We need to load the notes when the table view is loaded for the first time, too, not just when we add a new note. - [36:10] We also need a view to show and edit individual notes, so we’ll add a second View Controller and a segue from our table view controller to it. We’ll add a text view, and it turns out that our storyboard has little buttons that allow us to add constraints, or rules for where the text view can expand to, on the screen.
- [39:20] Now we’ll need our model to save any changes to the note, so we’ll write a
save
method. We’ll prepare a statement withUPDATE
, and to pass data into our query we’ll use?
s as placeholders, and bind data to it withsqlite3_bind_text
orsqlite3_bind_int
. This will help protect us from SQL injection attacks. - [43:40] In our table view controller, we’ll use
prepare(for segue...
to pass a selectedNote
to our note view controller. We’ll create an outlet for the text view (to display the contents for the note) and save the note automatically when the user leaves the view, by overridingviewWillDisappear
. We’ll also need to reload the data in our table view after we come back to it, so we’ll overrideviewWillAppear
in it. - [48:20] We don’t see our note being updated in the app after we change it, so we look at our note view controller. It turns out, that even though we save the
Note
correctly, we’re not updating that based on the text view. So we’ll set the struct’scontents
to the text in the text view.
Conclusion
- Apple’s iOS developer documentation will have lots of topics and examples that we can use to build apps on top of what we’ve learned so far.
- Supersection
- Staff Solutions