Lecture 3 - User Interfaces

Intro to User Interfaces

The user interface is the point of contact between the app and the user. In my opinion, it's the most important part of your app, simply because a good or bad UI will make or break your app. But, this isn't a design class, so we won't focus right now on what makes a good or bad UI: what we're going to talk about is how to build interfaces using Xcode.

In iOS development, there are three main ways to build user interfaces: code, nibs, and storyboards. There's a decently large debate in the iOS community about which method is the best way to build user interfaces. All three methods have their advantages and disadvantages, so we'll cover all three methods in order for you as the developer to decide what works best for you.

Storyboards

Storyboards are Apple's preferred way of building interfaces. They're the way that you built the PhraseThrow and WhereAmI apps, as a matter of fact! They provide the simplest, fastest, least-messy, drag-and-drop functionality for build interfaces. Storyboards are especially great for apps with multiple views (which are, pretty much, every app ever, and also of which we have made zero, so far), since they allow you to manage the transitions between those views, called segues. Everything is managed in one file. An example of a full storyboard might look like this:

Notice the multiple screens, and the transitions from screen to screen. Storyboards map out the whole entire app for you.

However, Storyboards don't allow much in terms of customization. Making very customized user interfaces (such as in an app like Snapchat) tends to be very difficult and ends up leading into coding your user interfaces, and wasted time. Also storyboards tend to be bad with reusability and data flow.

Storyboards are the newest way of making interfaces, and is the default way to make interfaces in Xcode 6.

Nibs

Nibs, or their more modern counterpart xibs, are basically storyboards without the transitions. Basically, each view controller you work with can come with its own specific screen that you can drag-and-drop labels and buttons and whatnot on. Then, you manage the transitions from screen-to-screen through code.

This is mostly a middle-of-the-road approach to user interface building, since it's part drag-and-drop-abstraction and part code (rather than Storyboards which involve almost no code, and is all abstraction). This, of course, allows for a little bit more customizability, especially with the transitions between two screens. Nibs also solve the reusability problem that storyboards have.

The biggest advantage of nibs is the ability to make custom views for more than just full-sized screens. For example, table cells, which we'll discuss today. These nibs can very easily be integrated with your app. In fact, the app we build today will be a mix of nibs and storyboards, combining the two easiest ways of building an app interface.

The downsides of nibs are similar to those of storyboards: you don't have total customizability yet. But it's a step above storyboards.

Code

No abstraction. All code. You have access to every single piece of every single view, and you have total control over every transition. Total customizability exists in this realm.

However, as you can imagine, there are major cons to coding your user interfaces: it's time-consuming, refactoring is hard, and you won't know what your interface actually looks like until your build and run your app.

This is my preferred way of building user interfaces, mostly because you have the under-the-hood advantage: nothing is hidden from you (has anyone had those "coding-compliant" issues using Storyboards before? Yeah, those don't exist when you code everything).

Short Intro to the Interface Builder

Today, however, we will focus on the first two methods of building user interfaces: nibs and storyboards. To use these two, we must use a very powerful built-in tool with Xcode that you've already gotten fairly comfortable with: Interface Builder.

Interface Builder is a built-in tool for Xcode that allows you to make all of the views for your iOS devices. It is descended from a piece of NeXTSTEP software of the same name, and was released for iOS development in 2008. It's commonly regarded as the simplest way to make interfaces your iOS devices.

You worked with Interface Builder in the PhraseThrow and WhereAmI apps, when you drag- and-drop'd everything to your iPhone screen. That was all Interface Builder. You were using Interface Builder to put views such as buttons and labels on a blank iPhone screen, which you now know is known as a nib, or alternatively, xib.

By the way, nib is short for NeXT Interface Builder. Xibs and nibs are used interchangeably--a xib is a nib, but saved to file in a different format (specifically XML), which makes things easier on things like Git, for example. Nowadays Xcode uses xib exclusively, but most people will still refer to it as a nib.

So, a single iPhone (or iPad) blank canvas is known as a nib. Recall that a storyboard, is basically a bunch of nibs, and their transitions (known as segues), shmushed together into one giant graph-like structure. It is an abstraction of your iPhone app that allows you to visualize how your app "flows".

TinyReminders

Today, we'll be building a very, very simple app called TinyReminders. Simply said, it's an app that keeps track of different reminders and dates in a table. You can view individual reminders, and you can add new reminders. This will be our very first multi-view app. Today, however, we will only implement the table of reminders, and viewing individual reminders.

Designing the App

Let's talk about the design a little bit. There are basically two views we want in our app. One is the main view that displays all the reminders in a list. The other is the single reminder view, which allows you to view the details of a single reminder.

In general, it's a good idea to map everything out with pen and paper first, so that you have a general idea of how you want your app to like. Today, we'll map everything out on the blackboard, that way we can all agree on what the app should look like.

Download The App

You can download the app here after the lecture. There will be no tutorial on how this app was made.

Table View Controllers

We can use a Table View Controller to display information about our list of reminders. What does a Table View Controller look like? Open up your Contacts app. That's a Table View Controller, listing all your contacts. Open up your Settings app. That's a Table View Controller, listing all the different settings you can change. Table View Controllers are just there to list things.

Table View Cells

Each "list-item" in a table view controller is called a TableViewCell. These cells are entirely customizable. This is why the table in your Contacts app looks different from the table in your Settings app, and they all look different from the table in your Reminders app (which actually has a button built in). In general, to make your favorite Table View, you configure a TableViewCell, and then tell your TableViewController how to use that Cell.

This is done most easily with Storyboards. In Xcode, you can drag-and-drop a TableViewController into your Storyboard. Then, you can add labels, buttons, and whatnot into the Prototype Cell field, like so:

Ignore the title bar at the top. Your app will not have that by default.

You can even adjust the height of the TableViewCell. But, basically, what you have above is the prototype for what each cell in the TableView will look like. The final step is to make a class which subclasses UITableViewCell and set the Custom Class for your prototype cell to be your new class. If you click on your protoype cell (be careful not to click one of your labels), you should be able to see on your right hand side:

Under Custom Class, put the name of your subclass of UITableViewCell. Now, finally make the connections (control-click) from the UI to your new class. This way, you have a fully connected and implemented class that corresponds to that specific UITableViewCell. Congrats! That was easy.

While you're modifiying your UITableViewCell, go here:

The reuse identifier is an important identifier which we'll talk about in a few paragraphs from now. For now, fill it in with something handy like "reminder" or "contact" or "message" or something descriptive.

Now, similarly, you're going to want make a custom class for your UITableViewController. Do the same thing as above: make a new class which subclasses UITableViewController, and set the Custom Class for your table view controller to be your new subclass. Now, you can customize your Table View Controller!

The Cell Queue

Suppose you're really popular and you have 9,000+ contacts. Loading all of those contacts into the table view controller will legitimately murder your app with out-of-memory errors--you'll be making 9,000+ UITableViewCell's!

To solve this problem, the smart engineers at Apple figure out that, at any given point in time, you only really need around 10 or so average-sized UITableViewCell's--about 6 or so are displayed on the screen, and the rest are off-screen ready to be displayed at a moment's notice.

So, these 10 or so cells are the only cells used, in tandem with a reuse queue. Basically, when a cell goes far enough off screen, the cell is put in the reuse queue, since it's not needed at the moment. Then when a new cell is supposed to come on-screen, you can specify to the UITableViewController that you want to dequeue a cell from the reuse queue and repopulate it with the necessary information. This way, you don't need to make a bajillion cells--just a few.

One last problem is that your app uses several different reuse queues. If your app uses multiple tables, how will it know which queue it should dequeue from? Remember the reuse identifier I mentioned a few paragraphs above? That's how. We'll see a code example in a little bit which ties it all together.

Displaying the Cells

Table View Controllers required a data source in order to display information. On a very basic level, a table view controller requires two bits of information in order to display a list of stuff: how much stuff is there, and, at a given position, what information should be displayed there (i.e. what should be in the first cell, the second, etc). Any class that can answer these questions for the UITableViewController can subscribe to UITableViewDataSource.

Classes that subscribe to UITableViewDataSource should minimally implement the following method:

// This function is called when the table view controller wants to know
//   what information should be displayed at which cell in the table (i.e.,
//   what goes in the first, second, ..., nth cell, etc).
//
// Passes in something called an index-path. Basically, if you access
//   |indexPath.row|, you will get the number that the table view controller
//   is looking to get information for.
//
// Notice that this function actually *returns* a UITableViewCell.
//
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    ...
}
    

Suppose your information is kept in the form of some array of Objects. Then, one implementation of this function could look like:

var objects: [Object] = [ ... ]

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    // This line basically says: dequeue a cell with reuse identifier "object"
    //   (or whatever you specified above), and, if it's not nil, unwrap it and
    //   cast it as a MyTableViewCell. If it is nil, just make a new instance of
    //   MyTableViewCell instead.
    var cell = tableView.dequeueReusableCellWithIdentifier("object") as? MyTableViewCell ?? MyTableViewCell()
    var myObject = self.objects[indexPath.row]
        
    cell.objectTitle.text = myObject.title
    ...  // other information population
        
    return cell
}
    

Quick and easy: get a cell for reuse (or make a new one), get the object, transfer the data, return the cell. There's one more function that needs to be implemented for the Data Source to work well with the table view controller. The table view controller needs to know how many cells you have! You have to implement the following function:

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

This function passes you an argument called section and wants to know how many rows are in that section. Table view controllers can be sectioned! Look at your Settings app. It's a table view controller which comes broken into several different sections. This function passes in a section number (0, 1, 2, ...) and wants to know how many cells are in that section.

For an app like TinyReminders, there aren't sections--just one huge table. So, one implementation of this function could just be to give the size of all your data. For example:

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

And you're done! You've implemented a table view controller with custom table view cells. Pat yourself on the back.

Navigation Controllers

What's the difference between the following two views?

The one the right has a title bar, of course. You've seen that title bar before! Grab your iOS device and open up the Settings app. The title bar is always at the top of that app. It has different titles, and sometimes it has buttons on the top-left and top-right corners. This title bar at the top helps you navigate your app, doesn't it?

The title bar is called a UINavigationBar, and it's part of a special view controller called a UINavigationController. A UINavigationController is a special class because it's known as a container class, which basically means it contains other view controllers.

Whoa, okay, this is a lot being thrown at you. Let's approach it slowly. Have you ever seen the movie Inception?

That movie is about dreams within dreams within dreams, several levels down (and also Leonardo DiCaprio NOT winning an Academy Award). Well views are the same way! You can have views inside views inside views. Think back to PhraseThrow: the labels and buttons were views inside a bigger view (the screen). We won't get into this too much right now, but views have hierarchy and can be nested.

A container view is just a view who doesn't do much except contain another view. So, the UINavigationController is a class that is mostly concerned with displaying another view, called its root view controller. It does some other stuff, like putting up a title bar, and maybe having some buttons, but it doesn't do anything else besides displaying another view.

So, in the above example, the image on the left is just one view. The image on the right is a view inside another view. Specifically, the image on the right is the view on the left inside a UINavigationController. Hence, UINavigationController is a container.

The Navigation Stack

Now, of course, navigation controllers should be able to provide navigation. In a navigation environment, new views are provided to the screen in a stack-manner. What does this mean?

Again, open up your Settings app. Suppose you hit General. A new view is pushed onto the stack, and a bar button item appears in the top left corner of your screen that says "< Settings". If you tap on that button, your view will be popped off the stack, and your previous view will take the forefront. Makes sense?

Navigating

Using Storyboards, let's add a second view to our app. Just drag a View Controller over from our collection of view items. Then, make a class that subclasses UIViewController and connect it to your new view controller (just like you did with the UITableViewCell and UITableViewController). Now, you can decorate that view controller however you look and tie it into the code for your new UIViewController subclass.

But now, suppose you want your second view controller to appear whenever someone taps a cell on your table view. Storyboards make that easy! Just right click on your prototype cell, click on the circle next to "selection" and just drag it over to your second view controller. You'll see three choices: modal, push, and custom.

A modal view is a temporary view that requires user attention. This is like what happens when you hit the New Tweet button or something like that. It pops up and you can't navigate out of it until you're done interfacing with it (like hitting a Cancel button). Selecting push will simply push the second view controller onto the navigation stack, just like we had previously discussed. We won't worry about custom segues. So, that's it! You made your first segue!

Bar Button Items

You can alternatively navigate using bar button items on your Navigation Bar--the top bar that appears when you use a UINavigationController. From your collection of view items, find the bar button item, and drag it to the right hand side of your navigation bar.

Now, you can play around with it. And modify it however you like. When you're ready to connect it, go to the Connections Inspector and under Triggered Segues, you should see a field called ``action". Again, drag the circle over to the view you want and select modal, push, or custom.