Essentials • 1:03:02
Table views are fundamental to the presentation of information on iPhone. This session will explain the basic features of table views and show you how to use them for everything from creating a simple list to laying out a more elegant user interface.
Speaker: Josh Shaffer
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good afternoon. My name is Josh Shaffer and I'm an engineer on the iPhone team at Apple and I work on UI kit. And today we're going to be talking about iPhone table views. Hopefully you all at least had a chance to hear a little bit about iPhone view controllers or have seen a little bit. There was just a session on it right before this and hopefully you had a chance to learn about that a little bit.
And if you did, you know that a view controller is a great way to manage a screen full of information. But what do you do now if you have more than a screen full of information? Well, that's why we're going to talk about table views now, which let you handle and display more than one screen full of information.
So we're going to cover a few things today. We'll start out with the anatomy of a table view and what exactly a table view is. Now these things may seem a little bit basic at first, but it's important that we start out with a good basic understanding of what exactly a table view is and the different parts of it so that we can then talk a little bit more about how you'll actually use this in the best way that you can in your applications.
Josh Shaffer And once we have a good foundation to build on, we'll talk about three main topics. We'll talk about how to create your table view, how you'll display content in it, and then how you can interact with it and allow your users to interact with the table.
So first off, what is a table view? Well, the first thing that may come to your mind when you think about table views is maybe just a basic list of information, just some black text on a white background, just a very simple scrolling list of information. And while UI table view on the iPhone can certainly be used for that, it's actually significantly more powerful than that. And you can create some very compelling user interfaces just using UI table view if you're willing to do just a little bit of work in order to customize the look just a little bit for your application.
Josh Shaffer So let's start off by looking at a few places on the phone in the existing applications that ship from Apple and see how UI table view is used there. So in the iTunes application, the UI table view is used here to display all of the tracks that your users can choose from in their albums.
The clocks application, each clock in this list is a separate row in a UI table view. In the iTunes Music Store application, a UI table view is used to display all the content in the center area of the screen there. All of the individual tracks available for purchase are rows.
And even that area at the top with the image and the extra information about the album, all of that is displayed within a single UI table view. And finally, the settings application uses UI Table View extensively to display all of the settings that are available for users of the iPhone to change and set different options for the phone itself.
So as you can see, there's some very different types of user interfaces that can be created using UI Table View. But we do provide two basic building blocks that you can start from in order to build your custom UI Table View appearances. So on the left-hand side here, we have the plain style table view. And on the right-hand side, we have the group style table view. Now, as I mentioned, these are two just basic styles that are provided out of the box by UI Table View.
When you create your table, you can choose one or the other, and you'll just get this appearance by default. Now, everything we just saw is built on one of these two styles, just with a bit of extra customization to get the extra custom look that each one has.
So now that we've seen some places where this can be used, let's talk about the different pieces that were used to compose the different tables that we just saw. So at the top, we have the table header, and at the bottom, the table footer. These are just custom UI views. You can set them as a property on the UI table view itself. You just create your own custom UI view and assign it to the table to be used as the table header and footer.
Now, these appear at the top and bottom of your content within the table. As the user scrolls the table up and down, the table header and footer will scroll with that content. So they make sure that the user has the maximum available space to view the areas between the table header and footer since they can scroll it off screen.
Then between the table header and footer are the sections. Now, this may be a new concept, for those of you that are familiar with tables on other platforms. But by default, we divide the content in our table views up into individual sections. Now, a lot of people think, well, I don't really want to use sections. I don't have any need for that.
So they decide to tell the table that they want zero sections. You don't really want to do this, because if you do that, then you'll have no content in your table at all. So by default, if you don't want sections, just don't do anything, and you'll get the default of one section.
Now, the sections are composed of, at the top, a section header and at the bottom, a section footer. This is pretty similar to the table header and footer in that you can define a custom UI view that appears however you like and assign it to the table to be displayed as the section header and footer. Or alternatively, you can just return an NSString and it will be displayed as a title within the section header and footer and it will have a standard appearance defined by the table style that you have selected.
And between the section headers and footers are where the actual data is displayed in the rows or table cells. So altogether, these pieces make up a full UI table view. But you may be thinking that I mentioned that there's actually a second style, the grouped style. And this is actually no different. There's a slightly different appearance, but all of the different parts are exactly the same.
So if we take a look at this same image but convert to the grouped style, it doesn't really change at all. There's some different appearances, default appearances for the different parts, but they're all in the exact same spot. So once you've figured out how to create one table, you already know how to create both styles.
So now that we've seen what the UI table view pieces look like, let's take a closer look at the cells that you'll display your content in. So on the left-hand side, we have room for an image. Next to the image is room for a textual label. If you haven't provided an image, that textual label will stretch to fill the full content width of the cell itself.
And on the far right-hand side is room for an accessory view. Now, the accessory view, you can provide your own view, but we've also provided three standard accessory types that you can choose from with very little work on your part. So first of all, we have the check mark. The check mark can be used to indicate that a particular row in your table is the one that is selected or that that row is checked by the user.
Next, we have the disclosure chevron. You should use this to indicate that your users can tap on this particular cell in order to get more information. Generally, this indicates to the user that they can tap on this cell and another pane of information will slide in, probably in a UI navigation controller navigation hierarchy, in order to display the content related to that cell.
And finally, we have the Detail Disclosure Chevron. Now, this gives the user the idea that they can tap on the cell in order to perform an action related to it, for instance, to play a movie that's related to this particular content in this cell, or that they can tap on the Disclosure Chevron itself in order to get more detail about it. So it's slightly different from the regular Detail Disclosure Chevron in that there's actually two different behaviors that you can get, one when you tap on the cell and one when you tap on the Disclosure button here itself.
So these are all the different pieces that make up a UI table view. So now let's talk about how you'll create your UI table view. We found very often that it was common for people to create UI table views that took up the full content area available on the device.
And as we just learned in the session about UI view controllers, a UI view controller is a great way to manage this screen full of information. So we found that people were often creating UI view controllers just to manage this single UI table view that took up the full screen.
Josh Shaffer So in order to reduce the amount of code that you have to write every time that you want to use one of these, we created UI table view controller. It takes care of creating your UI table view for you, and it also helps you to conform to the human interface guidelines defined for the iPhone.
But first let's talk about how it creates your table view. So when you create a UI table view controller, you can specify that you want either the grouped or plain style. And UI table view controller will create a table view of the appropriate type for you. If you'd prefer, you can instead use the UI view controller method for loading your view from a nib.
So if you want to lay it out in Interface Builder, you can do that instead. Josh Shaffer So if you'd like to do that, then in Interface Builder, you would create a new document. You would define the file's owner to be of the type UI table view controller or actually more likely your UI table view controller subclass.
You'll then add a UI table view to your nib by dragging it out of Interface Builder's pane. And you'll then attach the files owner's view outlet to point to this new table view that you just created. With these three things set up in Interface Builder, you can then have UI Table View Controller load your table view right from there. And so you can then move on to defining the appearance and behavior of the table view within Interface Builder.
Josh Shaffer So then you can choose here whether you'd like the grouped style or plain style table view. You could set other options such as whether or not separators are drawn between cells in the table view or other behavioral options that you can look at in Interface Builder and see for yourself.
So the other thing that UI Table View Controller does for you is to help you conform to the iPhone human interface guidelines. So one example of things that it does for you is when a UI Table View first appears on screen, it should flash its scroll indicator on the right-hand side to give the user some context as to what they're seeing right now as compared to the full amount of content that's available to be scrolled into the Table View.
So over here on the right-hand side of the Table View, when it first becomes visible, the scroll indicator will flash like this to show the user this information. UI Table View Controller takes care of this for you, so if you use it, it will automatically happen. You don't have to do anything else.
The second way that it helps you to conform to the human interface guidelines is through the use of the Edit button. So often, you'll have a UITableViewController that you'll want to edit content within, adding and removing rows. And in order to support this, you'll have an Edit button up in a navigation bar.
UITableViewController creates this Edit button for you and helps to make sure that its state is always in sync with the table view's editing state. So up here on the left, when the user taps on this button, it will switch to a Done button, and the Edit state of the table view will change to Match. And the same will happen when the user taps Done.
The final thing that it does is to help you manage selection within a navigation hierarchy. So when your user taps on the John Appleseed contact, that contact's name is going to highlight briefly to show the user that they've tapped on it. And then another pane of information will slide in showing the full information about John Appleseed. Josh Shaffer So when your user taps on the John Appleseed contact, that contact's name is going to highlight briefly to show the full information about John Appleseed.
So this is how you'll create your table view controller. I emphasize UI table view controller because it really does provide a lot of behavioral standard behaviors that you really want to conform to. And it also does reduce the amount of code that you have to write in order to get everything to work correctly.
And also you get the benefit of being able to use it directly within your other navigation hierarchies or tab hierarchies. UI Table View Controller is just a UI view, so if you prefer, you actually could just allocate a new UI table view and insert it into your view hierarchy directly.
So now that you know how you'll create your UI table view, let's talk about how you're going to display your content within it. Now, there's two design patterns here that you'll want to follow, and they're probably already familiar from other sessions today. The first is Model View Controller, and the second is Delegation.
So let's take a look visually at how this is all going to play out. So for the model view controller design pattern, obviously here on the left we have our view, the UI table view. Then we'll add our model. In this case, it's going to be pretty simple just to keep it pretty straightforward.
We'll have just an array of data, and each element in the array will be the content for a particular section. And that content will be the name of the section and all of the rows to be displayed within it. So this is our model, but your model could be anything that you like. However you store your data, that's your model.
And then the final piece is the controller, our UI Table View controller, which ties these two together. So there are a few questions that the table is going to have to ask of the controller in order to display the content from your model. And the way it's going to do this is using delegation.
And it will be using the UI Table View data source protocol. And a UI Table View data source is just a special type of delegate. So if you're using UI Table View controller, your UI Table View controller is automatically set to be the data source for your table view.
So the first thing the table view needs to know is how many sections are there. So it's going to ask your table view how many sections are there. Sorry, it's going to ask your table view controller. And it will do this by calling the number of sections in table view method.
Your table view controller will then look into the section array, determine how many sections are needed to display all of your content, and return that to the table view. Once your table view knows how many sections there are, it next needs to know how many rows there are in each section. So your table view will ask the table view controller how many rows are there in section zero.
It will do this by calling the table view number of rows in section method. At that point, your controller will look into the data source, into the model, determine how many rows are necessary to display section zero's content, and it will return that to your table view. Now, as you can imagine, the same thing is going to happen for every other section in the table.
Once the table knows how many rows there are in each section, it now needs to know what content to display in each row. So it will ask the table view controller, what should I display in section zero, row zero? And it's going to do this by calling the table view cell for row at index path method.
So at this point, your table view controller will look into your model and determine what content should be displayed in that row. Your table view controller will create a new cell, lay out the content within it, and return that to the table view to be displayed. Now you may notice here that I mentioned that your table view controller actually creates the cell and returns it that is displayed. I mean this quite literally. The thing it's returning is actually an instance of UITableViewCell. Now this is a direct subclass of UIView.
This is important because the thing that you're returning from your TableView cell for row at index path method is going to be inserted directly into the view hierarchy by UI TableView. So when your method returns it, UI TableView simply sizes it to fit the area for the row and inserts it into the view hierarchy.
This allows you, in your TableView cell for row at index path method, to determine the exact appearance of the cell and its content just by configuring this UI view to appear the way you'd like. Now, we're not going to talk about cell customization here specifically, but if you come to the Mastering iPhone Table Views session later in this week on Friday, we'll talk all about the different ways that you can customize UI TableView cells to get all sorts of different custom appearances in these cells.
you may be thinking that now there's going to be a lot of views created if you have a large table. So, if you can imagine that you have a hundred elements in your table, and the user scrolls from the top of that table all the way to the bottom, every row in that table is going to move through the visible area, although most of them are only going to be visible for a very brief period of time. If you had to create a new UI table view cell for every row that appeared, the performance wouldn't be very good, and you'd be using an awful lot of memory. So, in order to solve this problem, we've introduced the concept of cell reuse.
So let's take a look visually here at how this is going to behave. So as the user begins to scroll their table, a new cell is going to become visible at the bottom. And the table view is going to request the cell to be displayed from its controller.
So in your table view self-arrowed index path method, you'll notice that there are no cells available for reuse in the reuse queue just yet. So you'll allocate a new one. With table view cells in it with frame reuse identifier method, you'll lay out the content within it and you'll return it to the table and the table can then display it. But now as the user continues to scroll that content up, the John Appleseed cell at the top is going to move off the visible area. So as we'll see now, once that happens, the table view is going to put that cell into the reuse queue.
It's actually removing it from the view hierarchy at this point. So the only cells that are ever actually in your view hierarchy are the ones that are visible on screen. All the other rows in your table are there conceptually, but there's no UI views behind them if they're not visible.
So now as the user continues to scroll, another table cell is going to become visible at the bottom. And the table will ask your table view controller for the content to be displayed there. This time in your table view cell for ROID index path method, there is a reusable cell available.
So you'll call a different method, dq reusable cell with identifier, which will return that UI table view cell to you. You can then configure it with your new content using the same cell object that you had created before and return it to the table view display. It's going to insert it again into the view hierarchy now at the bottom for this new row.
And as the user continues to scroll, more cells will be moved into the reuse queue and they'll be recycled. So as the user scrolls up, cells will cycle from the top back to the bottom, making sure that a minimum number of cells ever have to be created in order to display just the content that's ever actually visible.
So it often strikes people as if cell reuse is a somewhat complicated concept. And it's really not. There's just a couple of methods that you have to know about. The DQ reusable cell identifier and the table view cell initializer. And with just these two basic things, you can take advantage of cell reuse. And I really encourage you, if you're using UI table view, please use cell reuse. The performance of a UI table view scrolling will not be nearly as good if you're not reusing cells as it will be if you're not reusing cells. be if you do.
So now that we've seen conceptually what these things look like, let's take a look at some of the methods that you'll actually be working with in order to implement all this. So I mentioned the UI Table View data source methods already, Table View Number of Rows in Section, and Table View Self-Aeroic Index Path. These are the only two required methods on UI Table View data source. And with just these two, you can define a single section table and its content and get it to appear on screen with really a very small amount of effort.
And then for cell reuse, we have DQ reusable cell with identifier, which is a method on UI table view. So in your self-erode index path method, you can call this in order to get a reusable cell if one is available. And when one's not, then there's the UI table view cell designated initializer init with frame reuse identifier.
Josh Shaffer And then for cell reuse, we have DQ reusable cell with identifier, which is a method on UI table view cell designated initializer init with frame reuse identifier. So in your self-erode index path method, you can call this in order to get a reusable cell if one is available. And when one's not, then there's the UI table view cell designated initializer init with frame reuse identifier.
Josh Shaffer And then for cell reuse, we have DQ reusable cell with frame reuse identifier. So in your self-erode index path method, you can call this in order to get a reusable cell if one is available. And when one's not, then there's the UI table view cell designated initializer init with frame reuse identifier. So, if we look at an example here, we've got two different basic types of cells in this table view. We've got one that has, at the top, a basic label on the left and then a sub-label on the right.
So, we can name this cell with an identifier, and we can call it identifier one. Then the second type of cell that we have has the switch button on the bottom on the right-hand side, and we can name that type of cell with the name identifier two. Now, obviously, you can be more creative with your names. I'm clearly not creative, but there you are.
And once you've done this in your table view cell for row at index path method, then you can, once you've determined what type of cell you need for the row you're going to be returning, you can try and dequeue a reusable cell of the appropriate type. So you already know that the content or that the structure of the cell is already the type that you expect. And you can just worry about setting the content in that cell in your cell for row at index path method. This really makes using cell reuse with tables that have multiple types of cells really quite easy.
So now that we've seen all of this, if we put it all together, this is an implementation of table view self-heroic index path. So the first thing that you'll do is you'll have your reuse identifier here, and you'll try to dequeue a reusable cell with that reuse identifier. That will return nil if one is not available. So if that happens, then you can just allocate and initialize a new one, again using the same reuse identifier.
Now that you've got your cell, you'll configure its content. So we've got the cell. In this case, we're just going to set the cell's text to a static string, row text. But you could do much more complicated configuration here to make it look however you like. But you may notice in this particular implementation, we haven't actually looked at the section or the row that the table is asking us about. That's represented by the index path up in the self-reward index path method name.
So there's really only two things that you need in order to uniquely identify a cell within the table. The first is the section that it's in, and the second is the row in that section. We already had a foundation object that could represent an array of integers, and that's NSIndexPath. So we use that in order to represent the particular cell that a table is requesting.
And in our case, it's always a special case because there are only ever two integers in our index path, the section and the row. So in order to simplify this, we've added a category on NSIndexPath that exposes this a little bit more clearly. So we've added two properties, the section and row properties, so that you can just get the section and row from an index path directly.
And as you can see here, there's also a new convenience method for creating a new one if you need to. So in your code, when you want to reference the section in a row that a particular index path corresponds to, you can just type index path dot row or index path dot section.
So now that we've seen all this, let's take a look at a demo of how it's all going to go together. What we want to end up with is something that looks like this. We'll have basically a table that has multiple sections, and each section will have 20 rows in it.
We'll have different titles for each section. And we're going to add one piece that we haven't talked about just yet, but it's actually really easy to add, so we just want to mention it real quickly. On the right-hand side, we have this index bar, which allows the user to quickly choose a particular section in your table to jump to it directly. So let's take a look at how we're going to build this demo.
So I've already created the Xcode project that we're going to use for this demo. There's really very little in my view controller. I've got a custom subclass of UI table view controller that we're going to call the list view controller. And we can just start implementing our table view data source methods.
Well, actually, before we do that, we're going to define our model, and this will be a very similar thing to what we just saw. It'll be very simple. We're just going to create an array. It'll just be an NSMutable array, and each element in the array will correspond to a particular row in the table. Now, we're going to start out very simple by only having a single section, so we don't have to have-- so we can ignore all the section options right from the start here.
So we're not going to implement number of sections in table because we're only going to have a single one, so we'll get the default behavior. So the first method that we have to implement is number of rows in section. This will tell the table how many rows to display in the one section that we'll have.
Since our array is just an array of elements and each string in the array is a particular row in the table, we can just return the count of elements in that array from table view number of rows in section. And this will tell the table how many rows need to be displayed in our single section.
Second, we need the table view self-heroic index path method. Now, this is where we're going to actually customize the content that's going to be displayed in the table. So first, we've created our reuse identifier that we're just calling my identifier. Next, we'll have to get the actual UI table view cell that we're going to use to display this row. So first, we'll try to dequeue one from the table. So we'll call table view dequeue reusable cell with identifier with the identifier that we just set up.
If one isn't available here, then we're going to have to allocate one ourselves. So we'll call table view cells init with frame reuse identifier designated initializer and again provide the same reuse identifier that we just defined. Now that we've got our cell and we know what we're going to display, we can get the content that we want to display within it. So we can just look in our contents array and pull out the element that corresponds to the row that the table is requesting. And we'll assign that to the cell's text property.
Once we've configured the cell this way, then we can just return it for the table for display. So we've only implemented two methods here, but we can now build it and we can run. And already, we'll have a table that can display a simple collection of 20 rows. And the user can scroll up and down, and we get this scroll indicator on the right-hand side. So with a very small amount of work, we've already got a table view working.
: So the first thing we'll have to do is redefine our model a little bit. So first we're going to need an array of section titles. This is just going to be one title per section, and they'll just be the letters of the alphabet. And then we're also going to modify our array so that it's now an array of arrays. So each element in the outer array is the data for a particular section, and each element in an inner array is a row in that section.
So we do have to add now one more method. We'll have to add the number of rows in table view method. Oh, sorry, the number of sections in table view method. And this is going to be now just returning the count of the elements in our contents array since each element there corresponds to a particular section in the table.
But that means now we have to modify our-- oh, I'm sorry. One more thing before we do that. We also want to add a title in each header. So in order to do that, we can add the table view title for header and section method. And we already have an array of section titles, so we can just return the element that corresponds to the particular section that the table is asking us about.
Now, we also have to modify tableViewNumberOfRowsInSection since our model is now an array of arrays. So in order to get this right, we now have to pull out the outer array-- sorry, pull out the inner array from the outer array that corresponds to the section and then count the number of elements in that, which is the number of rows in that particular section.
And we'll have to make a pretty similar modification here to get the correct text for the cell. Again, we're going to pull out the inner array corresponding to the particular section that the table's asking us about. And then we'll get the object corresponding to the row that the table is asking about and assign that to the text field for the cell.
So now when we build and run, we've got a new UI table view that now displays multiple sections, each of which contains 20 rows. But now this table is getting kind of long. And for a user to scroll all the way through this could be kind of time consuming. And it would be hard for them to find the exact place that they want to be. So we're going to add that index bar that I mentioned. In order to do that, we only have to actually add one more method.
So we can add the section index titles for table view method. And that just returns an array of the titles of each section. But we already have an array that contains all the titles of the sections. So we can just return that array directly. And with that simple change, when we build and run, now we've got this section bar on the right, and the user can scrub through that to jump quickly to a particular section in their table view. Now, I should mention that this particular section index is only available on the plain style table view, not on the group style table view. So now if we switch back to slides.
So now we've seen how you'll create your table view and we've seen how you're going to get your data to display within it. The last piece that we have to get to get our full functionality here is to allow the user to interact with your table. So what's going to happen when the user taps on a particular cell? How are you informed about this and how are you going to respond to it? Well, that's what UITableViewDelegate does for you.
So when the user taps on a particular cell, we can look at a couple examples of what happens in this case. In the settings application, we've got a list of ringtones that the user can choose from in order to pick the one they want. When the user taps on a row here, that row will highlight briefly, the check mark will move to that row, and then the selection will fade out.
In the address book, we saw this example earlier. When the user taps on the John Appleseed contact, it's going to highlight briefly and a pane of information will slide in. The idea here, again, is that when the user taps the back button, we should slide back to the table view, that row should still be highlighted, and then it should fade out.
The most important concept to take away from both of these things is that selections in UI Table View are temporary. When the user selects a particular row in your table, you should perform some action and then immediately deselect the cell after that action has been performed. Selections in UI Table View are not used to indicate a persistent selection. It's just a temporary thing.
So the methods that are going to help you to implement all this, on the UI Table View delegate protocol, we have the table view didSelectRowAtIndexPath method. This is called when the user taps on a cell, and the table view has already highlighted the cell for you at that point, and you're just being informed that the user has tapped there.
So at that point, you can perform whatever action is appropriate for the cell that's been tapped on, and then you'll call the next method, the UI Table View method, that does the same thing. So we have the table view that we just created, and we have the table view that we just created.
Now, as I already mentioned before, in the case of UI Table View Controller, if you're using this in the navigation hierarchy and you want the behavior where sliding back to a table keeps the cell selected and then fades out as you've slid back, you don't have to call deselect row at index path.
UI Table View Controller handles that part there for you. But that's a special case. In all the other cases, you'll want to deselect the row yourself. So let's take a look at how all of this fits together in a modification of the recipe sample that we just saw in the UI view controller session right before this.
So I hope you all had a chance to see it, but if you didn't, I'm going to quickly show you what they did. The basic view controller-based application that they created is a very simple recipes viewer. So at the top, we have a view controller that has a single button to allow us to see chocolate cake, which brings us into a recipe detail view that gives us more information about chocolate cake, and then another button that allows you to see a picture of chocolate cake.
Now, this is pretty basic, and there's no way to scroll any of this content, so the amount that you can see or that you can present to your user is limited to the amount of available space that's on the screen. So we want to modify this by adding some table views to really let you see a lot of extra content in these screens.
So what we want to end up with once we're done is something that looks more like this. At the top, we'll have a table view that displays all of the recipes that we know about. When the user taps on one, they'll get another table view that is now a group-style table view with more information that provides a full list of ingredients and the category that you want to categorize this recipe within, and another thing that you can tap on to get more instructions.
And the final thing that we'll want to add is this category pop-up. When your user taps on this particular cell, the idea is we want to slide in another table view that allows the user to choose what type of recipe this is. So we can say that it's a soup, which is obviously wrong, but it's clearly an entree, so we'll set that. And so that's what we want to end up with once we're done here.
So we'll start out with the basic recipe sample app that they created in the last session. The only thing that we want to modify in our application delegate is right here, but we'll start out with a quick overview of what's happened here. They've created a UI navigation controller, and this is the thing that will create the navigation bar at the top of the window for us. And we'll then be pushing all of our table view controllers onto that in order to slide them in.
Then they've created their top level view controller, which is the thing that we saw that displayed the single button that allowed them to choose chocolate cake. So we want to replace this with a new UI table view controller subclass that we'll create that will display all of the recipes in a list. Oops. That's not it.
So we're going to create our recipe list table view controller. And we're going to initialize it with the plane style as a plane style table view. And we've added one extra parameter to our initializer that allows us to pass in our model object, which is just a recipe controller that we've instantiated right here. Then that view controller will get pushed under the navigation stack without animation since it's coming up right as the application is launching. We don't want it to be animating in. And that's all we really have to do.
So now we'll implement the recipe list table view controller so that we can actually get content into that list. We've got a small amount of code here already. But mostly, we're going to build this from scratch right now. We're just going to retain that model object that we just passed in from our application controller. And we're going to set our title and the row height to custom values here. And then we've also implemented dalloc just to release that recipe controller when this table view controller goes away.
But really, there's nothing specific to what we're worried about right now. So the first thing that we'll have to do is we want to tell the table how many rows to display. Again, we're ignoring the number of sections in this particular instance because this is a single table that has only one section. So we don't have to do anything special there.
So we'll start out by implementing table view number of rows in section to tell the table how many rows it needs to display. Now, our recipes controller knows how to tell us how many recipes it knows about, so we can simply return that value here for the number of rows.
So now that the table knows how many rows there are, it's going to be asking us for the individual cells for each row. So we'll implement table view cell for row at index path in order to return that. And this is going to look very similar to the ones that we've already seen. So first off, we'll declare our reuse identifier, and we've got a couple of local variables that we'll be using in a second.
Next, we're going to try and dequeue our reusable cell. And if one isn't available, we'll allocate a new one. Again, we've already seen all this, but I just want to keep emphasizing that you really do want to be using this cell reuse. So we're going to show it every time.
Once you've now got the cell that you're going to be displaying, next you want to fill out the content. Now, we saw something a little bit more complex in this particular example than what we've seen before. Now we want to add an image as well as text. So this isn't really any more difficult.
We'll ask our recipe controller for the recipe corresponding to the row that we're being asked about. We'll get that recipe's name and assign it as the cell's text. And we'll get a thumbnail image from our recipe and assign that as the cell's image. That's all the configuration we really want to do here. So then we'll just return the cell for display.
So now if we build and run this, Oh, yeah, no. Sorry. We're not going to build and run that just yet. The other thing that appears in these lists is on the right-hand side you saw, we want to be able to give the user some idea that they can tap on a particular row in this table and slide in information about that particular recipe. So we're going to want to use the detailed disclosure chevron-- sorry, not the detailed disclosure chevron, the disclosure chevron-- in order to get the user this idea that they can tap on this cell.
Since we want the same accessory to appear in every row in our table, we can implement just one method, the table view accessory type for row at index path method. And we'll always return UITableViewCellAccessoryDisclosureIndic ator to allow the user to have some idea that they can tap on this cell.
But now that we've shown the user that they can do this, we should really implement some logic to allow something to happen once they do it. So in order to do that, we want to add the table view didSelectRowAtIndexPath delegate method. So we'll add an implementation of that that asks our recipes controller for the recipe corresponding to the row that the user has tapped on. And then we're just going to call the showRecipeAnimated method, which we'll implement now, and it's actually very small.
First of all, we're going to want to create a new UI table view controller subclass. So that will be the recipe detail view controller, which we'll use in order to display all the detail about this recipe. And this one we want to be of UI table view style grouped. So we'll just allocate one of these here. I've already created the basic file, but there's no content in it yet.
We'll assign the recipe that we just got to our recipe detail view controller so it knows what to display. And we'll push that view controller onto the navigation controller's navigation stack. Now, since we're never going to have to deal with this particular table view controller again from this view controller, we can release it here. The navigation controller has retained it, and it's maintaining now the only reference. So once the user returns to the main screen, this view controller is just going to be released automatically.
So now if we build and run, we've now got a basic table view that's displaying our image on the left with the textual label for each recipe on the right. And we've got the disclosure chevrons coming down the right-hand side. And now the user can tap on one of these rows, and more information will slide in. So now let's implement this extra table view here to actually display the extra information about each recipe. So we'll move on to the Recipe Detail View Controller.
This one has a small amount of extra information already in here, but not too much again. In our initializer, we're just setting up, again, the navigation item's title, which will display recipe up in the navigation bar. And in our dialog, we're releasing a few objects that we'll be creating in a minute.
We've also got an implementation now of the viewWillAppear method. This is called when the table view is about to become visible. Well, the first thing that you want to do is make sure to call super viewWillAppear to make sure that UITableViewController has a chance to perform whatever actions it's going to be performing. And in this case, it's going to be flashing the scroll indicator for us.
Now, the next thing we're going to do is something that I actually want to suggest that you don't do very often, but we're going to do it here for simplicity's sake, is reload the data in our table. This is the only place you're going to see me call reload data anywhere in any of these samples, and that's actually a very important point. In almost every case, you shouldn't really be calling reload data on your table very frequently. For the most part, any updating that you have to do can be done just on a row by row basis. You don't have to reload the data for the entire table.
If you start calling reload data a lot, the performance is going to start to degrade pretty quickly, because a lot of work has to be done to recalculate the full height of the table and to ask your controller for information about every section and all the rows in the section.
A lot of work happens every time you call reload data. So if you can avoid it, you really should just not use reload data unless you absolutely have to. But as I said, for simplicity's sake here, I'm going to just take the easy route and call reload data.
And the idea here is that when the user has changed the recipe type, we want to update the content here when they return to this view to reflect the new recipe type that they've chosen. So we'll call reload data so that our table gets reloaded. And we're going to set the title of our view controller to the recipe's name so that that will appear in the navigation bar.
I've also got an implementation of a method that we're going to wire up in Interface Builder a little bit later, which shows a photo of the recipe. And this is using a view controller that was created in the last session on UIViewControllers, which just displays the full screen image of our recipe.
So with all that in place, we can start to implement our UI table view data source and delegate methods. So the first thing that we'll want to do is, as you saw now, and let's take a quick look at that again just to refresh your memory, this particular table view is going to have multiple sections.
We're going to have one section here for the category, one section for the ingredients, and one section at the bottom for the lone instructions button. So we've actually got a constant number of sections. So we can implement our number of sections in table view method to just return-- Whoops, three.
And now that we've got our sections, you saw those sections each had their own title. So next we'll implement the table view title for header in section method to return the titles to be displayed for each of those sections. So for the type section, we'll return the name category. That's a little odd, but yeah. For the ingredient section, we'll return the name ingredients. And our other section didn't have a title, so we'll just return nil for everything else.
Now that we know what the different titles in the sections are, we need to tell the table view how many rows there are in each of these sections. So we'll implement the table view number of rows in section method. Now, this is very similar to things we've seen already, but now we're going to be switching on the section.
So for the type section and the instruction section, there's only ever one row in these. They're just a single row that the user can tap on to perform some action. So we'll only be returning a constant number 1 for this one. For the ingredients section, we want to ask our recipe for all of the list of ingredients and count them. And we'll return that as the number of rows for the ingredients section. And so then we can return that.
Now that the table knows how many rows there are in each of these sections, again, it's going to be asking us for the rows to display there. So we have to implement table view cell for row at index path in order to return that information. Again, starting out very similar to what we've already seen, we're defining our own reuse identifier, dequeuing a reusable cell with that identifier, and then calling a knit with frame reuse identifier if one wasn't available.
But now cell configuration is slightly different because we'll be doing something different depending on the section. So we're going to add a switch statement and switch on the indexpath.section that the table is asking us about. For the type section, we'll ask the recipe for its type and set the text to that.
For the ingredient section, we're going to ask the recipe for its list of ingredients and get the ingredient that corresponds to the row that the table is asking us for. We'll then get the name of that ingredient from this object and assign that to the textual label for the cell.
And we are doing one other bit of customization for this particular row. We're going to set the cell selection style to UITableViewCellSelectionStylenone. The idea here is that when the user taps on an element in the ingredients section here, we don't actually want to allow them to tap it.
We don't want to show any visible feedback when the user taps there. Nothing is going to happen, so we want to make sure that a cell selection never appears when they're tapping there. If we set the cell selection style to CellSelectionStylenone, then they'll never get this visual feedback. For the instruction section, we're going to set the text to the constant string instructions. Now, I apparently have a bug in here, so we're going to add one extra thing.
Because we're reusing cells, we want to make sure that the cell selection style is updated in case we're reusing a cell from a section that already had it set to selection style none to display now a row that should be selectable. So we're going to change the selection style back to blue before we modify it by default. So that just makes sure that if we've reused a cell and we had previously set its style to none, we reset it back to blue so that the user can now tap on it.
So now we've got our table content being displayed, but a few of these allow the user to tap in order to slide in another pane of information. So in order to support that, again, we have to implement table view accessory type for row at index path. Josh Shaffer So for the type section and the instruction section, which are the two that the user can tap on, we'll return UITableViewCellAccessoryDisclosureIndic ator to display the disclosure indicator on the right-hand side. Josh Shaffer And for all the rest, which is just the ingredients ones, we'll be returning UITableViewCellAccessoryNone so that we don't have an accessory.
And now, similar to what we did before, we're actually going to want to perform some action when the user has tapped on these cells. So in order to accomplish that, we have to implement table view didSelectRowAtIndexPath. So we're going to find out what section was selected. And we're going to set up a local variable here for a view controller.
So if the section that the user tapped on was the type selection, we're going to allocate a new type selection view controller, which is another custom subclass of UI table view controller that we'll be creating in just a minute. And that's the thing that's going to allow the user to tap on the particular type of recipe and choose the one that they want. And this is, again, going to be a group style table view. So we'll initialize it with UI table view style grouped. And we'll set the recipe for that to be the current recipe that we're looking at.
Next, if it's the instruction section, which we saw all the way at the bottom, then the user, when they tap on this, will be sliding in a pane of information that shows them the instructions for creating this recipe. So this is just an instructions view controller, which was created in the last session, and we'll initialize that from a nib.
I'm not going to look too much into this because there's no table view here. So we're just creating this new view controller. And then in both of these cases, if we've allocated a view controller, we're going to push that onto our navigation controller's view hierarchy and again release it since we don't really need to retain a reference here. This way it will just go away when the user returns.
So now when we build and run, we've got this top level table view that shows all of our different recipes. And when the user taps on one, now we've got a full table view showing all the content that's available. Now the other piece that's still missing here, which we saw earlier when you saw the final version, was at the top we had an image along with the name shown at the top of the table. Now we can implement that using a custom table view header. So let's go back to our code and go back to the top here where we had view will appear.
So there's another method that we can implement called ViewDidLoad. Oops. And this is called only once. So view will appear. We want this to happen every time the view becomes visible. So as the user navigates back and forth, this will get called every time our table view becomes the visible view. The viewDidLoad method is only called once when this view is first allocated. And so we only want to do this once, so we'll implement the creation of our table header in viewDidLoad.
This is actually an important distinction between -- sorry, there's actually an important distinction between this and Awake from Nib. We actually, if you're using UI view controllers, encourage you to use viewDidLoad here in order to do this configuration instead of Awake from Nib. So now in order to create that table header view that we want to create, we're going to load it out of a nib that I've already created. So we'll get the main bundle and load the nib named DetailHeaderView and set its owner to ourself. Now, if we take a quick look at that nib in our resources, - Oops. Oh, hey, great. There it is.
So what you're going to see here is that we've got just a basic view that's being laid out. with a single image view and a textual label next to it. So our view has the single image view here on the left and the label on the right. Now we've got some outlets already set up.
So this name label is assigned to the file's owner name label outlet so that once it's loaded, we'll have a reference to it in our view controller. So in the view controller here, we've got these ID outlets set up already that point to the UI view, which is our table header view. That's this. The UI button, the photo button, that's this.
[Transcript missing]
So once we've done that, we can call self.tableview in order to get a reference to our UI table view controllers table view and assign our new table header view to the table view's table header view property. This will allow it to appear directly at the top.
Josh Shaffer And we'll also want to set the table header view's background color so that it matches the background color of the rest of the table view. You know, there was that vertical striped appearance on the background of the group style table view. So we can ask UIColor for the group table view background color, which is going to give us that background pattern and assign it to the table header view's background color.
So the last piece that we want to do is we actually want to configure the content that's going to be displayed in that table header. So-- oops. We'll add that up here to our view will appear so that it will happen once every time the view appears. So we'll add the photo buttons on the photo button that we just saw here, this UI button. We'll set its image. : Hi, I'm Josh Shaffer. I'm the editor of Table Views. I'm going to show you how to use the table view interface to create a simple list of information on iPhone.
So with those changes, we can now build and run. And now, once we tap on the recipe in our main list, we've now got at the top this new header that scrolls with the content.
[Transcript missing]
So the last piece that we want to add here is the category. We want to allow the user to tap on this and select the category. Now, laying this out is similar to everything we've already seen, so I'm not going to set that up again. This is already done.
But as you can see, as we're tapping on it, nothing's happening. The cells are remaining selected, which, as I mentioned before, is something we definitely don't want. So let's go back and look at our type selection view controller and make this actually add a check mark on the right hand side when the user taps on one of these.
So we're going to modify our table view self-heroic index path method so that there's already a checkmark accessory view on the right-hand side for the cell that is the selected one. Actually, I should mention one thing before we get to that. In the viewWillAppear method, we've got this cache selection index path object. So that's just an instance variable on our type selection view controller. And it's just an instance of NS index path.
The idea with this is our particular model is storing the type of the recipe as a string. But for convenience and for speed reasons, we don't want to have to search that list of strings every time that we're looking up the selected cell. So we'll precache the index in our list of recipe types for the type of this particular recipe.
So we're going to just go over the list of recipe types and see if each one is the type of the recipe that we're currently looking at. So we're going to just go over the list of recipe types and see if each one is the type of the recipe that we're currently looking at. So we're going to just go over the list of recipe types and see if each one is the type of the recipe that we're currently looking at.
And if it is, we'll set the cell selection index path to an index path for that row in section zero since there's only one section. And we're just going to maintain this selection index path as the user changes the selection so that we've always got a quick way to look up which one is the selected cell.
So the modification we have to make now in order to actually make this happen-- oh, sorry. I started on this before, and then I kind of jumped back. So let's get back in sync here. When this thing first slides into view, and there's this new UI table view showing the list of available types, if one has already been selected-- so if we already have a type for this recipe-- we want to show the checkmark by default there. So in our table view self-arrow-at-index-path method, we want to make sure that the checkmark accessory type is already set for the cell that corresponds to the type of recipe that we have.
So if we already have this selection index path that we just set up a minute ago, and if it's equal to the index path that the table is asking us for, then we're going to set that cell's accessory type to UITableViewCellAccessoryCheckmark. If not, we'll set the accessory type to UITableViewCellAccessoryNone in case we're reusing cells again. We want to make sure to remove the check mark if we're reusing one that had it.
This is slightly different from how we dealt with accessory types before. We had before implemented the table view accessory type for row at index path. You can do that here too. I'm just doing this to show you that there's actually another way to do it. And it allows you to configure it here and make sure that you're setting it when you're configuring your cell to be what you want.
So now that we've got this set up for the initial case, we want to do something when the user actually taps on the cell. So in order to do that, we have to implement table view did select row at index path. So we'll check to see if we already have a selected index path. And if we do, we want to remove the check mark from that. Now, this is an example of what I was saying before where you really don't want to just reload data simply because something has changed.
It's much quicker here to just remove the accessory from the previously checked cell. So if we had this selection index path before, we're going to ask the table for the cell for row at index path selection index path. And this will return us the actual UI table view cell that's being displayed or nil if there isn't one.
So we can then set the check cells accessory type to UITableViewCellAccessoryNone. So this avoids having to reload data just to remove that check mark. And then we'll update our cached selection index path to be the index path that the user has just tapped on. But now we want to add the check mark to the row that the user did just tap. So in order to do that, we're going to call self-arrowed index path with the index path that the user tapped on to get the cell that they tapped. And we'll set that cell's accessory type to UITableViewCellAccessoryCheckmark. Again, avoiding reloading data just to add this check mark.
Next, we have to update our model to reflect that we've made this change. So we're going to get the list of recipe types and get the object at the index corresponding to the row the user has tapped on. And this is just going to be the string representing the recipe type. And we'll assign that to our recipe's type.
And finally, most important part, we want to deselect the row because row selection is temporary. So we'll call table view deselect row at index path and say animated yes in order to fade out that selection. So now that we've got all that in place, when we build and run this, we've got our top level view controller that shows the list of all of the recipes.
We can drill down in, and now we've got the content of this chocolate cake recipe. And now we can tap on this category, which was previously unknown, and set it to entree. So when we go back, now you can see we've got it set to entree. And as we navigate back and forth, that check mark is there. And as we switch these different, you can check on different things, and the check mark will move. So, that's all for that.
So we've put a lot of stuff together here today, but there's three main points that you should really try and take away from all this. First off, if you can, please try to use UI Table View Controller. If you're thinking about rolling your own or creating your own full screen table view, take a minute, step back, and think about whether you can use UI Table View Controller to do it for you. It'll reduce the amount of code you have to write, and it'll make sure that the behavior of your table view is consistent with the behavior of other table views.
Second, always, always reuse cells. There may be a few, I hesitate to say, reasons why you might not want to, but think again. You probably want to reuse cells. And finally, selections are temporary. When the user taps on a cell, that cell should be selected and remove that selection.
So there are a few related sessions later in the week that you should think about coming to. The Mastering iPhone View Controllers will have a lot more information about view controllers and how you can use table view controllers within more complex view hierarchies. That's on Thursday, so you should come see that.
And we have a Mastering iPhone Table Views session on Friday, which will give you even more information about the more powerful features available in UI Table View, including complex cell editing, cell addition, cell deletion, cell reordering, and a variety of other things, especially cell customization, to get some very complicated and intricate UIs just using UI Table View.
And we've also got two labs that are dedicated to UI table views. If you have questions about UI table view or have specific questions about your code and how you're trying to use it, please drop by these labs. I'll be there and some of the other UIKit engineers will be there. And we can answer your questions about UI table view directly.