MyToDo, a Basic iOS App Tutorial in Swift 3

Written by Bruno Philipe

08. January 2017


In this tutorial we will cover the implementation of a basic iOS App in Swift that allows us to keep a to-do list.

The tutorial will cover the following areas:

  1. Setting up an iOS App project in Xcode
  2. Building a simple UI structure using Interface Builder
  3. Creating a data model
  4. Connecting the data model to the UI using a View Controller
  5. Implementing serialization to achieve local persistence

This tutorial will only use built-in features of iOS, and so we won’t use any third-party libraries. Those can be very useful, but my objective is to make this as simple as possible and without any dependencies. Even if you have never written any apps for iOS, you should be able to follow along. Also, I will assume you are interested in learning some small details regarding iOS and Swift, and will throw some trivia and explanations along the way.

So let’s get started!


1. Setting up an iOS App project in Xcode

At the time of the composition of this tutorial, the latest software versions available are:

  • Xcode: 8.2.1
  • Swift: 3
  • iOS: 10.2.0

If you follow this tutorial later on, some details might be different, so keep that in mind.

To start, open Xcode and select “Create a new Xcode project” from the welcome window (or tap “new project” in your fancy Touch Bar if you have one of those).

In the following screen, pick “Single View Application” in the “iOS” tab.


Make sure the “iOS” tab is selected

Just click “Next”. In the following screen, just create a name for your App. I will use “MyToDo”. Select “None” from the team lists (it should be the only option if you never coded for iOS before). Make sure Swift is selected from the Languages list and “iPhone” from the devices list. Also deselect all checkboxes.


We can make the app universal and add tests later if we want.

Just click “Next”. Pick a place to create the project directory. I have created a “Workspace” directory in my home directory, and its path is ~/Workspace. You can put it wherever you want. After you picked the desired location, just click “Create”. I recommend leaving “Create Git repository…” enabled so you can reuse this template in the future, but I won’t mention git again during the tutorial.

That’s it for this part of the project! Believe it or not, you now have a compilable project. If you wanna test it, just make sure you have a simulator selected next to the “Play” button (that’s the “compile” button, in fact).


I will use the “iPhone 7” simulator throughout this tutorial.

When you click the “Play” button, the compilation should complete successfully, and after that the simulator should open and load your app. All you’ll see is a blank screen, though, but we will change that soon.


Wasn’t that simple?


2. Building a simple UI structure using Interface Builder

Long time ago, back in the times of yore, Xcode was only one of the many Apple developer tools. There were other separate apps, like Instruments, Interface Builder, Application Loader, and others. Nowadays they were all merged with Xcode (or still live as separate Apps that are launched from Xcode). But that’s just a piece of trivia, let’s go to the tutorial.

We want a simple interface, because no one likes having to burn a few neurons trying to figure out how an App works. Also, we’re just getting started.

Click “Main.storyboard” in the sidebar. You should be greeted by an empty rectangle with an arrow pointed at it.


That’s modern art right there.

Each rectangle like that in a Storyboard file represents a screen (or a View) our app can display (and each has an associated View Controller). The arrow shows which of those rectangles (View Controllers) is the initial controller. Since we only have one, that’s the one with an arrow pointed to it. Technically that arrow is optional, but that’s an advanced feature.

Because we want to show a to-do list, we will probably want to use a view more complex than an empty rectangle. So let’s add a “Table View Controller” to our App.

First, make sure you have the right sidebar enabled by clicking the rightmost button in the toolbar. Then select the yellow “Table View Controller” item from the list/grid in the bottom of the sidebar and drag it to the main view. The cursor should change to an “add” symbol.


If you can’t find the “Table View Controller” icon in the grid, you can try clicking the “list” button highlighted in green on the picture, and that will show the names of each icon in a list instead of a grid. You might also already have this view in list mode by default.

We now have a table view controller in our app. It is not connected to anything else in our app nor configured correctly, so you will get a couple of warnings in the project, but that’s fine. We will fix this next.

We also won’t need that other view controller (that other empty rectangle), so you can click on its title (where it says “View Controller”) and hit “delete” on the keyboard.


If you don’t make sure to click in the top part (highlighted in red), you might end up deleting only the contents of the view controller, which is not what we want.

Also, keep in mind that once you click the top part, the text will be replaced by three icons (used for bindings), and that means you clicked the right place! =)

Now click the top part of our new table view controller and enable the “Is Initial View Controller” checkbox in the Attributes inspector (all those highlighted).


Our table view controller should now have its own arrow.

Now we need to configure our table view in order to show the to-do list. The first thing we need to do is decide how the list will look like. Since we aim for a basic app, let’s not use any custom views or cells. I might do a tutorial on that later.

Now just a basic overview on how table views work in iOS. The entire iOS SDK (which is actually called cocoa-touch) is based on the Data Sources – Delegates structure. This is a very neat way of displaying lots of data in the view without having to load all of the data into the view memory at once. In simple terms, the Data Source is responsible to tell the view about the data it needs to display, but the view still decides how much and which items it displays based on what is currently on screen. When it decides it wants to load more data (say, because the user scrolled the view), then it asks for another small chunk of data from the Data Source.

The Delegate in the other hand is who decides what to do when the user interacts with the view (say, when the user taps an item in the list). You might want to show another view, or to modify the data. That’s the responsibility of the Delegate.

You can think of the Data Source as a huge dictionary where all the data is stored, and that the view only reads page-by-page as needed, and of the Delegate as a small bell the view rings whenever the user does something.

Inside the view controller, select the empty white rectangle under the “Prototype Cells” text and select the “Attribute inspector” icon in the top of the right sidebar. In the “Style” selector, pick “Basic”, and into the “Identifier” text field enter “cell_todo”. The “Identifier” string is how we will inform the table view which of the prototype cells we want to use for each specific row.


As the name says, prototype cells are just prototypes that hold no data. They are just a blueprint of how the actual cells should look like. When the actual table view loads, it will copy the prototype you tell it to use for each row, and ask you to write the data to it. You can have multiple different prototypes in a single table view to display different data in case that’s needed (say, we could support audio entries to the to-do list, and that would show as an audio spectrogram, for example). Because we will only support simple text to-do entries, a single prototype cell is enough.

Since we are using the default “Basic” style of table view cells, we don’t need to do any other setup in the UI.

If you’re a careful reader you’re probably wondering how we will display the information that the to-do item is marked as “done”. For that we will just use an “accessory cell item”, which is another built-in feature of the table views. More on that soon.

If you compile the app now, you should get no warnings, and running it should present you with a list of empty cells. That’s the iOS way of saying “there’s nothing in this table view…”, but we will work on that next.


3. Creating a data model

This is where we will start writing our first lines of code. Cool, huh? The iOS SDK is very well designed and it’s impressive how much we can achieve only using built-in things. Unfortunately, it is not as advanced as to have the App build itself, so we need to work a bit here.

We will need a new class file to handle our model. It will be a very basic one: A class named ToDoItem with two member variables: title of class String and done of type Bool.

To do so, right click the “MyToDo” folder in the left sidebar and click in “New File…”. (You can hide the right sidebar to give you more space if you need, just click the rightmost button in the toolbar again).

In the following screen, select “Swift File” from the “iOS” tab.

Click “Next”. Enter “MyToDo” as the file name.


Make sure the group is set to “MyToDo” and the “MyToDo” target is selected.

Click “Create”.

Now we have an empty Swift file, and the only thing it does now is import the “Foundation” framework. That’s the framework that implements the basic Swift language and basic iOS classes. Since we’re not dealing with UI in our model, that’s good enough for us.

So, first thing to do is create a class. To do that, just insert into your file:

class ToDoItem
{

}

That’s it??

Oh yes, that’s it! You see, swift is a very “non-verbose” language (should I say “quiet”?). And that includes default access control parameters. So, if we’re creating a simple class inside our App, swift assumes it is an internal class, so there’s no need to write private class or internal class. Also, in Swift a class does not have to inherit from any other class (unlike Java, for example, where all classes inherit from the Object class by default).

Now we need to define our member variables. We can do that just by writing:

    var title: String
    var done: Bool

Now you should get a compiler error saying that the class has no initializers. That error is a bit misleading, though. Indeed, our class does not have an initializer, but the only reason this is a problem is because the member variables done and title don’t have default values. Since we don’t want them to have those, we need to create an initializer instead:

    public init(title: String)
    {
        self.title = title
        self.done = false
    }

This should silence the error, and let us progress to the next step!

In the end, your class should look like this:

class ToDoItem
{
    var title: String
    var done: Bool

    public init(title: String)
    {
        self.title = title
        self.done = false
    }
}

Did you notice we didn’t write any accessors (getters and setters)? That’s because Swift does that for you automatically. You can still do that manually, if you want, but that’s discouraged. If you wanna do some kind of setup after a variable is set, you should use willSet and didSet instead. These are called “Property Observers”.


4. Connecting the data model to the UI using a View Controller

In this step we will finally see something happening in our app. It might look like a long setup (and indeed it is), and there are libraries that allow you to do that much more quickly, but I think it’s nice to see how the system works in the most basic level (and also see what those libraries are actually doing for you).

Before we touch the view controller, we need some data to display. What’s the point of writing view code if it won’t be able to show anything in the first place? So let’s just create some mock data in our model.

After the class declaration (class ToDoItem {…}), create an extension of the ToDoItem class by doing so:

extension ToDoItem
{
    public class func getMockData() -> [ToDoItem]
    {
        return [
            ToDoItem(title: "Milk"),
            ToDoItem(title: "Chocolate"),
            ToDoItem(title: "Light bulb"),
            ToDoItem(title: "Dog food")
        ]
    }
}

Extensions are an awesome Swift (and Objective-C) feature that allows you to add methods to any class. Literally. In this case we just want to add a temporary class function to our class without messing the main class ToDoItem declaration, leaving it to deal only with the important class shenanigans.

In simple terms, we just added a class method that returns an array with four “undone” to-do items, and your “ToDoItem.swift” file should look somewhat like this:

Since iOS follows the MVC (Model-View-Controller) architecture, we need one last piece to finish our puzzle. If you haven’t noticed, we spent the last two sections laying down the basics of the other two pieces.

Since we already have a View Controller in our project (from that empty rectangle in the first section), we can just reuse it for our own purpose. If you select “ViewController.swift” from the left sidebar, you should see something like this:

The first thing we need to do is change the class we inherit from. That’s the word that follows the colon in the class declaration line, UIViewController. While we technically could use that class, there is another one that will make our job much easier. Just replace that for UITableViewController.

class ViewController: UITableViewController
{
    …
}

Now we must link this view controller class to our rectangle in Interface Builder. To do that, click “Main.storyboard” in the left sidebar. Click the top of the rectangle, where it says “Table View Controller”.

In the right sidebar (click the rightmost button in the toolbar if the right sidebar is hidden), click the “Identity” inspector and enter ViewController into the “Class” text field.

Press Enter to confirm your input.

Nothing will have changed in the app if you run it again (sigh…), but believe it or not, a lot of progress is being made! We now have a model, mock-up data, a set-up view, and a controller that’s just missing a few methods to link it all together. Let’s write those next.

Go back to the “ViewController.swift” file by clicking on it in the left sidebar. (Again, you can hide the right sidebar if you need space).

The table view needs its Data Source to implement a few methods in order to work. If you don’t implement these methods, the table-view will fallback to a “static” state, and won’t do much. The reason we replaced the class inheritance to UITableViewController is because it implements some of the Data Source and Delegate methods required by the table view, leaving you to do only the ones you really need, which are the following. Just add them to your ViewController class:

    private var todoItems = ToDoItem.getMockData()

    override func numberOfSections(in tableView: UITableView) -> Int
    {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        return todoItems.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {

    }

Notice I left the method tableView(cellForRowAt:) empty. I will explain its implementation next.

But first, lets quickly see how Swift functions/methods are declared. Every Swift function/method has the following structure:

func functionName(label1 parameter1: Type1, label2 parameter2: Type2) -> ReturnType
{
    Body
}

There are some things that differ from other languages. The most obvious one is that parameters have a type, but also have a label. The labels are part of the function name, but are placed before each of the parameters for clarity. If we were to reference the above function, its name would be functionName(label1:label2:). Another use of labels is to make overloading easier. You can now have two functions with the exact same initial name (here, functionName) and same parameter/return types, but differentiate them by different labels (and vice-versa, but only if it is not visible to Objective-C, but that’s a bit complicated).

Also, the underscore character _ can be used whenever we don’t want to use a label (it is actually a lot more powerful than that, but I’ll leave this out for now), but that’s usually only “required” when dealing with older Objective-C APIs like the table views one.

Back to the subject. We need to put something in the tableView(cellForRowAt:) method. Remember the “cell_todo” cell Identifier we configured in the UI section? This is where that will be useful.

What this method does is request you (the data source) to configure a view for a specific row in the table view. Table views in iOS are organized by sections. Since we will only use one section as of now (hence the return 1 in the numberOfSections(in:) method), we will leave that explanation for later. All we have to worry about now is the row property of the IndexPath parameter on the method call.

Each call to tableView(cellForRowAt:) will reference a different cell in the screen, and it’s our job to decide what we want to show there. Since we are showing one row per to-do item (hence the return todoItems.count in the tableView(_:numberOfRowsInSection:in:) method), that’s very easy. Just add this to the method body:

        let cell = tableView.dequeueReusableCell(withIdentifier: "cell_todo", for: indexPath)

        if indexPath.row < todoItems.count
        {
            let item = todoItems[indexPath.row]
            cell.textLabel?.text = item.title

            let accessory: UITableViewCellAccessoryType = item.done ? .checkmark : .none
            cell.accessoryType = accessory
        }

        return cell

What this method does is “dequeue” (copy) a “reusable” (prototype) cell with the identifier “cell_todo”. That’s the prototype cell we set-up in our UI. Then it checks if that row makes sense with our list of items. This is just a sanity check, but even if it should “never” happen, this will prevent us from causing an exception if we ever unintentionally change the behavior of the table view somewhere else. Then it sets the title of the cell to the item title, and the accessory type to “.checkmark” or “.none” depending on its bool value. Then it returns that initialized cell back to the table view so it can be displayed.

Notice: The .none and .checkmark values look weird, right? That’s because they are members of an enumeration, called UITableViewCellAccessoryType. But since we already defined that this is the type of the accessory variable, and we’re attributing those values to it, Swift uses its type inference machine to figure out what we want without having to repeat the enumeration name every time we only need to reference one of its members.

If you compile and run the App, you should see a familiar list now:


Ha!

“But wait…” you could say, “there’s something missing”.

Indeed there is! There’s no title bar! You see, title bars are not only title bars. In iOS, these things provide some magic functionality that most users don’t realize: The title bars are managed by another view controller, a UINavigationController. And those are responsible for the navigation between view controllers. You wanna know why it is so magical? Then follow along.

Go back to the “Main.storyboard” file by clicking on it in the left sidebar. Click on that top part of the rectangle of the table view controller. Now go on the menubar Editor > Embed In > Navigation Controller

*woosh*

How cool is that? We have a title bar of our own. Also, that arrow that indicates the initial view controller has now moved to the navigation controller, and another arrow points from the navigation controller to the table view controller. That’s why this file is called a “storyboard”: it describes the relationships and flow between our view controllers, and it will help you a lot along the way of writing a large app.

If you run the App again, you should see a much more respectable UI.

We now need to implement a basic logic in our app, to allow the user to mark to-do tasks as “done”.

Go back to the “ViewController.swift” file and implement a new method in the view controller class:

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    {
        tableView.deselectRow(at: indexPath, animated: true)

        if indexPath.row < todoItems.count
        {
            let item = todoItems[indexPath.row]
            item.done = !item.done

            tableView.reloadRows(at: [indexPath], with: .automatic)
        }
    }

This new method will be called whenever the user taps in a row in the table view. The first thing it does is tell the table view to deselect the row the user tapped (if you previously tapped any rows in the simulator, you noticed that it remained selected forever, or until another row was tapped). Then it does the same sanity check as we’ve done previously. Next, it flips the boolean value of the “done” property of the item (if it was false, sets it to true, and vice-versa). Then it tells the table view that it should reload the cell for the specific row representing this item.

If you compile the app and run it now, you should get an interactive to-do list app!

The list still doesn’t have a title, though. We can add a static title by going to the viewDidLoad() method in the view controller and adding the line self.title = "To-Do" to it’s method body:

    override func viewDidLoad()
    {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        self.title = "To-Do"
    }

Building and running the App now should present you with a much prettier view:

Now we need to allow the user to add/remove items from the to-do list. First let’s write the code that will show an alert asking for the new item’s title, then add it to the list of items, and then tell the table view that it needs to show an extra row.

Add the following methods to the view controller class:

    func didTapAddItemButton(_ sender: UIBarButtonItem)
    {
        // Create an alert
        let alert = UIAlertController(
            title: "New to-do item",
            message: "Insert the title of the new to-do item:",
            preferredStyle: .alert)

        // Add a text field to the alert for the new item's title
        alert.addTextField(configurationHandler: nil)

        // Add a "cancel" button to the alert. This one doesn't need a handler
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))

        // Add a "OK" button to the alert. The handler calls addNewToDoItem()
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (_) in
            if let title = alert.textFields?[0].text
            {
                self.addNewToDoItem(title: title)
            }
        }))

        // Present the alert to the user
        self.present(alert, animated: true, completion: nil)
    }

    private func addNewToDoItem(title: String)
    {
        // The index of the new item will be the current item count
        let newIndex = todoItems.count

        // Create new item and add it to the todo items list
        todoItems.append(ToDoItem(title: title))

        // Tell the table view a new row has been created
        tableView.insertRows(at: [IndexPath(row: newIndex, section: 0)], with: .top)
    }

Notice: nil means “nothing” is Swift. It is similar to NULL in other languages, but it’s not exactly the same.

Now we need to invoke this code. Let’s create an “add” button in the title bar, since this will look consistent with other default iOS Apps. To do that, add the following code the the viewDidLoad() method, just under the line that sets the view controller title.

        self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(ViewController.didTapAddItemButton(_:)))

If you compile and run the app now, you should see the new “+” button to the right of the view controller’s title. Tapping it should present us with this alert:


If you don’t see the virtual keyboard, just press Command+K. You can also type using the computer’s physical keyboard.

Tapping “OK” should cause the alert to close and the new item (“Batteries” in this case) to show under all other items. The new item should also be fully functional, letting you tap on it to mark it as done/not done.

Now that we can add items, we should also implement a way to remove items. Thankfully, the table view controller also implements this mostly by default, you only need a method to make the change in the data storage.

To achieve this, just add this method to your view controller class:

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
    {
        if indexPath.row < todoItems.count
        {
            todoItems.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .top)
        }
    }

Swiping to the left over the items should now allow you to remove them.

Now that we have a way to insert and delete items into and from the list, we should remove the mockup data. Just replace the function call with an empty array initializer:

    // From
    private var todoItems = ToDoItem.getMockData()

    // To
    private var todoItems = [ToDoItem]()

If you run the app now, you should be able to add, remove, and mark items as done. But if you close the app and open it again, it will all be gone… But we will fix this on the next section!


5. Using serialization to achieve local persistence

This is the most important aspect in any app that deals with user data. You want to preserve the input and settings that users make so when they open the app again, the stuff they want is there.

There are many ways to do this, but we will use a very simple and straightforward one: We will serialize the ToDoItem objects (that is, transform them into data that we can store on a file), and put that into a file. When the user reopens the app, we will unserialize the data (that is, transform the data that was stored in the file back into objects we can use), and build the table view again with those.

Notice: Serialization is called “encoding” in the iOS environment

We start by making our model class comply with the NSCoding protocol. Protocols are like contracts, they specify what a class should do, but that’s it. It’s up to the class to actually honor the contract and implement the methods to make that behavior real.

The NSCoding protocol only defines two methods: encode(with aCoder: NSCoder) (to serialize the object) and init?(coder aDecoder: NSCoder) (to unserialize the object).

Before we implement those methods, we need to tell the Swift compiler that our class implements the NSCoding protocol, and also that it now inherits from the NSObject class because this is a requirement of NSCoding (hopefully this will change in the future). To do that, just make the following change in your “ToDoItem.swift” file:

// From
class ToDoItem
{
    …
}

// To
class ToDoItem: NSObject, NSCoding
{
    …
}

You will get a compilation error saying that your class doesn’t conform to the NSCoding protocol, but that’s just because we haven’t implemented the methods yet. To do that, just add the following to your ToDoItem class:

    required init?(coder aDecoder: NSCoder)
    {
        // Try to unserialize the "title" variable
        if let title = aDecoder.decodeObject(forKey: "title") as? String
        {
            self.title = title
        }
        else
        {
            // There were no objects encoded with the key "title",
            // so that's an error.
            return nil
        }

        // Check if the key "done" exists, since decodeBool() always succeeds
        if aDecoder.containsValue(forKey: "done")
        {
            self.done = aDecoder.decodeBool(forKey: "done")
        }
        else
        {
            // Same problem as above
            return nil
        }
    }

    func encode(with aCoder: NSCoder)
    {
        // Store the objects into the coder object
        aCoder.encode(self.title, forKey: "title")
        aCoder.encode(self.done, forKey: "done")
    }

It’s important to notice that when we return nil in the init?(coder aDecoder: NSCoder) method, this tells the caller that the decoding failed, and that the object is not valid. Therefore that specific object won’t show in the App after the loading has finished. However, if the other objects did decode properly, they will be available, making this implementation of persistence very resilient to data corruption.

That’s about it for the serialization part, but we still need to invoke this code. Still in the “ToDoItem.swift” file, after the extension where we added the getMockData() method, add this other extension:

// Creates an extension of the Collection type (aka an Array),
// but only if it is an array of ToDoItem objects.
extension Collection where Iterator.Element == ToDoItem
{
    // Builds the persistence URL. This is a location inside
    // the "Application Support" directory for the App.
    private static func persistencePath() -> URL?
    {
        let url = try? FileManager.default.url(
            for: .applicationSupportDirectory,
            in: .userDomainMask,
            appropriateFor: nil,
            create: true)

        return url?.appendingPathComponent("todoitems.bin")
    }

    // Write the array to persistence
    func writeToPersistence() throws
    {
        if let url = Self.persistencePath(), let array = self as? NSArray
        {
            let data = NSKeyedArchiver.archivedData(withRootObject: array)
            try data.write(to: url)
        }
        else
        {
            throw NSError(domain: "com.example.MyToDo", code: 10, userInfo: nil)
        }
    }

    // Read the array from persistence
    static func readFromPersistence() throws -> [ToDoItem]
    {
        if let url = persistencePath(), let data = (try Data(contentsOf: url) as Data?)
        {
            if let array = NSKeyedUnarchiver.unarchiveObject(with: data) as? [ToDoItem]
            {
                return array
            }
            else
            {
                throw NSError(domain: "com.example.MyToDo", code: 11, userInfo: nil)
            }
        }
        else
        {
            throw NSError(domain: "com.example.MyToDo", code: 12, userInfo: nil)
        }
    }
}

Notice that these functions will throw errors (similar to exceptions in other languages), and we will have to deal with that when we call them. But don’t worry, because that’s easy to do!

To call the serializer/deserializer methods, go to the view controller file (“ViewController.swift”), and add the following to the viewDidLoad() method:

        // Setup a notification to let us know when the app is about to close,
        // and that we should store the user items to persistence. This will call the
        // applicationDidEnterBackground() function in this class
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(UIApplicationDelegate.applicationDidEnterBackground(_:)),
            name: NSNotification.Name.UIApplicationDidEnterBackground,
            object: nil)

        do
        {
            // Try to load from persistence
            self.todoItems = try [ToDoItem].readFromPersistence()
        }
        catch let error as NSError
        {
            if error.domain == NSCocoaErrorDomain && error.code == NSFileReadNoSuchFileError
            {
                NSLog("No persistence file found, not necesserially an error...")
            }
            else
            {
                let alert = UIAlertController(
                    title: "Error",
                    message: "Could not load the to-do items!",
                    preferredStyle: .alert)

                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))

                self.present(alert, animated: true, completion: nil)

                NSLog("Error loading from persistence: \(error)")
            }
        }

And add the following method to the view controller class:

    @objc
    public func applicationDidEnterBackground(_ notification: NSNotification)
    {
        do
        {
            try todoItems.writeToPersistence()
        }
        catch let error
        {
            NSLog("Error writing to persistence: \(error)")
        }
    }

To test the persistence, use the Command+Shift+H key combination to go to the home screen in the simulator, and then launch the app from Xcode again (if you quit the simulator with Command+Q, you might kill the app process before it has a chance to call the todoItems.writeToPersistence() function, and it won’t work).

And that’s all! You now have a fully working App that allows you to keep track of your to-dos, and that maintains its state between loads.


Finished project

If you are having trouble and don’t know what you’ve done wrong, you can download the final version of this project by clicking here.


Extra considerations

You might have some questions regarding the syntax of Swift that I have not explained during the tutorial. And the most important one I skipped is the concept of Optionals in Swift, together with the ? and ! operators, and the if let statement.

I will write about those in a future blog post.

Edit: This new blog post is here! Read it now!

Swift: Coding With Options???