Essentials • 48:44
Table views on iPhone give you a wide range of options for customization, and enable you to create a more compelling, dynamic user interface. Find out how you can create checklists, manage editing and reordering, and efficiently implement your own table cells to support custom layouts.
Speaker: Jason Beaver
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 morning. Thanks for coming out this last morning of the week. I know it's been a busy week. We've thrown a lot of stuff at you this week. But we're going to cover some pretty important stuff this morning, so I'm glad you guys all made it out. As you may or may not know, virtually every application on the phone uses table views, and virtually everyone that does customizes them in some way. So we're really going to get into a lot of that this morning. How to make the table view do some pretty cool stuff, and really customize the look and feel of your application.
Didn't realize it advanced. My name is Jason Beaver. I work on the UI kit team. There were a couple of sessions earlier this week that talked about basics of table views, how to get some content in there. If you're a desktop Cocoa developer, you might have gone to the iPhone for Mac developers session, and they talked some basic stuff about how it's different than the desktop table view and how to load content in.
If you're new to the Mac or iPhone platform, you might have gone to the Understanding iPhone Table Views session. And again, they cover the same basic stuff there. We're going to kind of pick up from where that left off. We're not going to cover anything basic. We're just going to jump right into how to do some of the advanced features.
So we're going to cover three basic areas. We're going to talk about editing in the table view and how you can control that. We're going to talk about a couple of different ways you can customize the cells in the table view. And then we're going to talk about inserting and deleting. So let's start with editing.
You've probably seen this. This is the phone application. When you edit this, we indent the content, make room for some editing controls on the side. We also add a few rows. In other applications like the World Clock, you'll notice the time on the right hand side and the day of the week actually slides out to the right when we go into editing mode.
Some things like the stock applications actually don't transition into editing mode. You flip them over, there's a different table view that's already in editing mode. So there's a lot of different ways your users can interact with the content of the table view. So there's one method to put the table view into editing mode, and that is set editing animated. The first argument says whether you want to transition into or out of editing mode. And the last argument says whether you want to do this in an animated fashion.
If the table view is on the screen and the user is looking at it, you probably want to do that in an animated fashion. But if you're getting ready to bring that table view in from off screen, or you're just launching the app and you want to leave it in that state, you probably want to do that in a non-animated fashion.
So, we're going to show several kind of examples of how we can control editing. On the left-hand side, there's a plain style table view, and on the right is a group style table view. And if you just take a stock table view and call set editing animated on it, All the rows will inset a little bit, and we'll see a bunch of remove controls down the left-hand side. We'll also see a bunch of reorder controls down the right-hand side.
But you don't always want to allow the users to edit every single row in the table view, so there's a delegate method you can implement called Table View Can Edit Row and Index Path. So here we've got an example where a delegate has implemented this method, and we've turned no for the first four rows. When we transition into editing mode, notice the first four rows remain unchanged.
Now, if you don't do anything else, you get the remove control on the left-hand side, but you can control that as well. We have an editing style that you can set, and there are four or three basic editing styles. There's the none, which indicates you don't want anything to appear there, and the user can't insert or delete on this row. And there's the delete control we saw, and there's also an insert control.
So here we've implemented our delegate to return none for the first three rows, and delete for the next three, and insert for the remaining. And as we transition into editing mode, you'll see how that looks. Notice that when we said on the group style that you can't edit a row, the content doesn't inset. Excuse me, if you do say it can edit, but you return an editing style of none, it will inset.
You can also control whether that reorder control appears on the right-hand side on a per row basis using the delegate method table view can move row at index path. Here we've specified that the first four rows cannot be moved, and when we transition into editing mode, you'll see there's no reorder control over on the right.
So in the group style table view, there's one additional thing you can control, which is should that rounded rectangle indent when we go into editing mode? You notice in the stocks application, the editing controls appear inside that rounded rectangle. So there's a delegate method you can implement called table view should indent while editing row and index path.
So here we've got group style table view. And on the left hand side, we're going to specify that it should not indent. And on the right hand side, we'll specify it does. And you can see how the content still insets, but the rounded rectangle stays the same. If you implement this on a plain style table view, it doesn't actually do anything.
So everything up to this point has been about how to control the appearance of the table view and each individual row while going into editing mode. Now that you're in editing mode, the user can interact with those editing controls on the left. And by default, if they hit the delete control, a little delete button will animate it on the right to confirm that.
If they hit the insert control, you're asked directly to deal with that. But in either case, you're going to get sent a table view commit editing style for row and index path method. And let's look at a little sample implementation of what you might do here. In this case, we're only handling the delete, but you could do a similar thing for the insert. The first thing you need to do is delete the object from your model.
Because when you next tell the table view to delete that row, we're gonna turn around and query you about your model to make sure we've got everything right. And the remaining thing is you need to tell the table view to delete the row. And you do this by calling the delete rows at index path with row animation method. We'll cover this in a little more detail a little later. But basically this will tell the table view to animate that row out.
You also need to handle the case where the user is reordering using the reorder controls over on the right. And you do this with the TableView move row at index path to index path method. This implementation is pretty simple. All you have to do is update your model to reflect the new state, because visually the user's already moved and dropped that row there, so visually the table view already reflects the new destination. So you just need to update your model to match that.
Now, every point in the table view might not be a valid drop point for that row that the user's trying to reorder, so you can control this using the table view target index path for move from row at index path to proposed index path, which is quite a mouthful.
Here's a little sample implementation of that. You basically first need to validate that drop point. Now, this validate reorder destination for row method is something you'd need to provide. It's not something you can call on our objects because we don't know where it might be a valid drop point in your model.
And if that's not a valid drop point, you just need to return an alternate one. So again, we call this method alternate reorder destination for row. That would be something you'd provide. But if that is an okay place to drop it, all you need to do is return that proposed destination index path that was passed into you, and we'll open up a gap at that location.
So we're going to look at a demo now. If you've come to one of several sessions earlier in the week where we've been building this recipes application, we're going to sort of finish it up a little bit this morning by adding the ability to edit a couple of places. So the first thing we're going to do is in that recipes list at the top, we're going to allow users to delete the various recipes. And in the detail pane, we're going to allow the user to delete some of the individual ingredients.
Let's just remind everybody where we're starting out with. So we have a recipes application here. It's got a top-level list of recipes. Each row has a picture and a recipe name. And if we click on one of these, there's a detail view where we can see a category, an ingredient list, instructions, and there's a few other things we can do here as well.
So the first thing we want to do is in that top level view controller, which is our recipes list table view controller, we need to put an edit button. So we'll specify that the navigation item's left bar button is an edit button item. And because we're a table view controller, this will automatically transition our table view in and out of editing mode. But we need to handle those two delegate methods that we're going to get when users delete or reorder recipes. So we're going to start by implementing the table view commit editing style method.
We're only going to handle the delete in this case, so we'll check to make sure we are getting the delete. We'll simply remove the object from our recipes at the index as specified. And then we'll tell the table view to delete that row with row animation left, which will cause it to slide out to the left. Scroll up here? Okay, sorry.
We also need to implement the delegate method to handle reordering. So we'll implement the table view move row at index path to index path method. Let me, yeah, we'll bring these down a little bit so everybody can see what these are. So we need to get the recipe that's represented by-- that we're looking at here. And we need to remove that object from the recipes at the index where it was, and just add it back at the new location.
We need to do a similar thing over in the recipe detail view controller so that we can reorder that list of ingredients. So in our init with style, we'll also add an editing button in, this time on the right side. And we'll implement those same two delegate methods. The table view commit editing style for row and index path.
And here we're going to check to see if the section is the ingredients section, and also check the editing style to make sure it's delete. And if it is, we'll remove that ingredient at that index and tell the table view to delete the row at that index path. We also need to implement the move row at index path.
And we'll just get the ingredient that was specified there, remove that from the ingredients list, and add it back at the new location. So if we run the demo now, so you have an edit control, and if we click on that, we'll transition into editing mode. And if we go into a particular recipe, we can transition into editing mode now. And we can reorder these and delete these as well.
If you notice down in the detail, we have edit controls everywhere on our category and instructions. We really don't want that. We really only want the user to be able to delete the rows in the ingredients list. So let's see how we would fix this. We want to implement one additional delegate method.
That's the table view editing style for row and index path. And if the index path dot section is the ingredient section, so we're looking at a row in the ingredient section, we're going to return editing style delete. Otherwise, we're going to return editing style none. So now let's see how that looks.
So it looks better, but we still have reorder controls everywhere. We really only want them to reorder ingredients. So let's step back and also implement the table view can move row and index path method. Again, we'll check to see if we're looking at the Ingredients section. If so, we'll say yes, that can be moved. Otherwise, we can't move that row. So let's see how this looks.
Okay, this looks pretty good. So let's see, we can pick up a row and reorder it, but ooh, we can actually drag this down into other sections. That's probably also not what we want. So let's go implement that additional delegate method to control where is a valid drop point. So this is the table view, target index path for move from row at index path to proposed index path.
We'll check to see if the place we're trying to drop the section for that is prior to the ingredients section. We'll just open a gap at the very first row of the ingredients section. That's row zero in the ingredients section. And if we're trying to drop at a section that's after the ingredients section, we'll just open a row at the end, basically the last row in the ingredients section. Otherwise, we'll just return the one that's passed in. So if you're within the ingredient section, we'll just open a row right below your finger.
All right, let's try this now. Still pick up a row here, but good. I can't drag it down here. I can't drag it up here. If I let go, it'll snap back to the gap we opened. All right, let's go back to slides. So now we're going to get into a little bit about how you can customize the cells in your table view. And there's, like I mentioned before, two ways.
You can just add subviews indirectly, and you can subclass the cell. So let's first talk about how to add subviews directly to the cell. I want to briefly cover again sort of the anatomy of a cell. If you went to the Understanding Table Views session, you're familiar with this slide.
On the left hand side is an image, and in the middle is a text label that will stretch to fill the full width if there isn't an image. And on the right is an accessory view. There's several built-in types, and you can of course put your own views over there.
But in the middle, that you can't really see, is a content view. And this content view is where you're going to add your own subviews. Because remember, a cell on the iPhone is actually a view, and so you can return an entire view hierarchy here, and it'll get placed at the appropriate spot in the table view.
The content view will inset automatically as you go into and out of editing mode. So if you put your sub views in there and set up appropriate auto resizing masks, they'll move around as appropriate to stay within that content view. Here's what it looks like in a group style. It's basically entirely the area inside the rounded rectangle, but when you transition into editing mode, we'll make room for some controls on the left and right.
So let's look at a rather complex cell. This is one of the YouTube cells. Everything to the left of that accessory view is the content view. And if we sort of break this down, you'll see we have a content view that fills that entire area, and it has a bunch of sub views, basically labels, image views, a little rating control.
So great, we know we're going to add these sub views to the content view, but how do we configure these for optimal performance? If you went to any of several performance sessions this week, you know we're really performance sensitive on the device, and we need to do everything we can to make performance fast. So the first thing you need to do is make sure all of these views you add are opaque.
Now, at first this seems like maybe not what you want, because when we select, we put this nice blue gradient behind the row, and you want to make sure that you don't obscure part of that with a big, you know, solid opaque rectangle. But the table view takes care of a lot of this for you. It will actually crawl through the entire view hierarchy, marking views non-opaque when they're a sub-view of a row that's selected.
Also make sure the backgrounds are all clear and do some other cleanup. And then we'll put all that back when the row is no longer selected so you get nice, fast scrolling performance. You want to make sure that those rectangles or those subviews have the same background color as your cell. Now this is typically white.
And you want to specify an alternate highlight display. So here's an example. This is out of the Settings application. If you look at that middle row where it says Show and 50 Recent Messages, Notice that the labels are black on the left-hand side and blue over on the right-hand side.
But when we select those, we want them to go to white. So all you need to do to do this, is, if you're using a label, set the highlighted text color. If you have your own control, you just need to make sure that control has a highlighted property, and set some alternate display when that's set.
And if you watch carefully as this row deselects, it'll do that in an animated fashion, and when we get to right at the midpoint of the animation, we'll toggle that highlighted flag, and all your controls will be asked to redraw with the new highlighted state. And then once the animation completes, we'll go through, mark everything opaque again, reset the background colors, so again, you get nice, fast scrolling performance. So we're going to look at another little demo here. We're going to continue to extend the recipes application. In the ingredients list, we're going to add an amount field over on the right.
So we're in our Recipe Detail View Controller. We're going to be adding a subview here, and so we need a way to get a hold of that later when we're reusing that cell. So we're going to add a tag that we'll just assign to that cell, and then we can access it later.
Now in our cell for row at index path method, we're going to check to see if we're dealing with the ingredients section, because we want to return a different type of cell in this case. So we'll create a different ingredient cell identifier to identify those types of cells. And we're going to attempt to dequeue a reusable cell with that identifier. And if we don't get one, we'll allocate a new one. This is basically the same pattern you've been seeing all week.
We're then going to add an amount label over on the right. So this first line just creates a rectangle for that, and then this will allocate that label. And we'll set the auto-resizing mask so there's a flexible left margin. So as the cell goes into editing control, that'll stay sort of pinned to the right side of that content view.
We'll assign it a tag, again, so we can find it later. And we'll set its alignment to text align right. This will keep it no matter what we put in there, it'll stay right aligned with the edge of that content view. We'll set a text color here to that bluish color that you've seen a lot of places. And we'll set that highlighted text color so that when the row is selected, we'll toggle over to a white color.
And at this point we'll just add that as a sub-view of our content view. And then release the label. So now we have a cell, whether we just created it or whether we dequeued it, we need to fill it in. So we're going to get the Ingredients Dictionary, and we're just going to use the cells built-in label to specify the ingredient name.
And then we're going to ask the cell for the view with tag, amount tag, to get a hold of that amount label that we threw in there. And we're just going to put the amount in. This amount's already in our model. So all we need to do at this point is return this cell. Now, this is for the Ingredients section, so we can come down here below where we were handling the Ingredients section. We don't need to handle it here now since it's handled above.
All right, now we've got the amounts over on the right hand side. Notice as we transition into and out of editing mode, Those automatically stay against the right-hand side. All right, let's go back to slides. So that's great, it's really easy to do, but sometimes you need a little more control of what happens when you go into or out of editing mode, or some other state changes in your cell. This is when customizing your cells by subclassing is a great way to go.
So, how do we do this? Well, the first thing is that your content's added in exactly the same way. When you create your subclasses cell, you're still going to create separate views and add those as subviews to the content view. But now you have some control over the positions of those things as state changes, because you can simply override the appropriate methods and reposition these things as appropriate.
So I mentioned that world clock example. Again, notice that the time and the day of the week slide out to the right. We couldn't have done this by just adding those views as sub views of the content view. We really needed some control there to cause them to move outside the visible area when we transition into editing mode.
There's one caution though. If you subclass cell, and it is just a view, and views implement draw rect, you're not going to get the behavior you expect if you override draw rect in your cell. And the reason is that when a user selects that cell, we insert that blue gradient.
That's inserted as a view right above your cell, but below the content view. So that's actually going to obscure any drawing you try to do directly in your cell. If you find you need to do custom drawing, it's really easy. You can just create a view to do that drawing, and add that view as a subview of the content view.
Another thing you might need to do when you subclass cell is do a little additional cleanup when the cell is reused. Your table view cell has a method called prepare for reuse. We call this basically as soon as the cell disappears out of the view and is thrown in the reuse queue, we call prepare for use to allow the cell to clean up a little bit.
If you have additional cleanup you need to do, you have to reset some things in your cell to a default state so that the next time it's reused, you know the position and size and color of everything. You can override this method, do whatever little bit of cleanup you need to do there. said reset those to the default values, but you need to make sure you always call super when you do this. UI Table View Cell does its own clean up, and if you don't do that, can't guarantee the state of the cell.
So we're going to continue to extend the recipes demo now. We're going to take that top level recipes list, and we're going to create a custom subclass of table view cell, so that instead of just the image of the recipe and the name, we'll also see a description and a prep time, and we'll do some cool things as we transition it out So we'll go back to the recipe list table view controller, because this manages that top level list of recipes.
And in our cell for row, we used to just create a standard table view cell and set the text and image on it. So we're going to throw away all of this code. And instead, we're going to create an instance of a recipe table view cell, which we'll get to in just a minute.
So the pattern will look the same. We'll attempt to dequeue one of those. If we can't find one, we'll allocate a new one. Because this is our own class now, we can set a property on that. for the recipe, and we can just fill in that recipe, and the cell will know how to extract the bits it needs. So we can now just return that cell.
So let's go look at this recipe table view cell. Subclass of UI table view cell, but we're going to need a few little additional instance variables here. The first is the recipe that was just assigned, and then we'll go ahead and create instance variables for the four pieces that we need to display the bits of content. will also define the property that can be set, that's set by the recipe list table view controller.
So we've got a few defines at the top that we'll use to position some things. But basically nothing else is in this class. So we need to create all of those views. So in our knit with frame, we'll first create the image view. and add that as a sub-view. Now notice we passed in CG Rec 0. We haven't yet tried to position it. We're going to override layout sub-views, and I'll talk about that in a little bit, to position all these things.
Next, we're going to create that name label. It's again just a label and it's with the CG0, Erect0. We're going to set its font, because we don't want the default font we get here. We want to use something smaller to make room for a description below that. We'll set its text color to black, and its highlighted text color to white so that it highlights properly. And we'll add that as a sub-view of our content view. Now we're going to have similar blocks of code here for a description label. and a prep time label. And the only difference with the prep time label is that's going to be right aligned.
So we need to figure out what the frames of all of those controls are going to be, both in and out of editing mode. So we're going to write a few little convenience methods to do that for us. So the first is the Image View Frame. And if we are in editing mode, we're going to return one rectangle.
And the only real difference between that and the one that we return if we're not in editing is we're going to get this little editing inset, so things look nicely aligned. We'll have similar methods here for the Name Label Frame, the Description Label Frame, and the Prep Time Label Frame.
So now we actually need to position these things. And the way you do that in UIKit is to override layout sub views in your view. You need to always call super, make sure anything else that's in there gets laid out properly. And then all we're going to do is call set frame on each of these things and pass in the frames that we compute in each of these convenience methods we just wrote.
And notice we didn't have to take care of doing any animation or anything like that. The reason for this is when you call set editing animated, we'll call layout subviews inside that within an animation block. So if you just specify the frame that you want, or any other attributes that are animatable you want, we'll actually animate all those for you.
We also need to define the methods for that property, so we'll add a set recipe that fills in the recipe IVAR, and then extracts the bits we need, the thumbnail image, the name, the description, and the prep time, and assigns those to the appropriate views on screen. And of course, we need a getter for our property. We added a few IVARs, we need to make sure we clean those up. Let's take a look at what this looks like now.
That looks a lot nicer. We've still got our image now and our name, but now we've got this great description and some information the user can use to figure out if maybe this is going to take too much time tonight. Let's see what happens when we go into editing mode.
It's starting to look kind of busy. So, when we're in editing mode, I don't think we really need to see maybe the prep time. So, let's see how we would take that out when we go into editing mode. - We basically need to go into our layout sub views, and just say if we are in editing mode, we want to set the alpha of that prep time to zero. And if we're not in editing mode, we want it to be one, so it's fully opaque.
Let's see what this looks like. There we go. So now that cleans it up, looks a little nicer when it's in editing mode, but if you notice, that's actually fading in and out. Again, we call your layout subviews within the context of an animation block, so anything that you set in there that's animatable automatically will be animated properly. All right.
So we talked about a couple of ways to customize your cells. So let's briefly cover why you might want to choose one versus the other. Any time your content can be completely specified, the layout of that content can be specified with auto-resizing masks, that might be a good candidate to do it just by adding those sub views.
And anytime you don't need to modify the default behavior of the cell, in that ingredient cell we did, we really just needed to add a label over there and make sure it indented properly. We didn't really need a subclass to do that. This was a great candidate for just adding subviews into the content view.
But any time you do need that custom layout code in that top level cell that we defined, we really did kind of want that prep time level to fade out. We might have wanted it to slide completely off the screen, things like that. This is a great time to subclass. And there's other things you might want to do to change the behavior of that. You might want to override what happens when the cell goes into editing mode or other state changes. Do whatever you'd like there. Those are great times when you'd want to subclass.
So when you're subclassing, you very frequently are gonna be dealing with multiple use identifiers. This has been covered a little bit briefly, but I just wanna reiterate it. Here we have, The settings application, and there are several different, or a couple different types of cells. All the things at the top that are in that orange rectangle have sort of a similar appearance to them. They have a black label on the left and a blue label on the right. In the case of that ad account, that just doesn't have a value to it.
Any cell that you use there could be used for any of those positions. So when you need a cell there, if you have a reuse identifier defined for that type, you can reuse any one of those cells. The ones at the bottom also have their own sort of type. A label on the left and a switch on the right. And again, anyone there can be reused at any other position in the table view.
So, we want to make sure we have really good scrolling performance. And there was a great session yesterday, which I'll talk about a little bit here, that covers some of this. And we want to make sure We're always reusing cells. We've reiterated this over and over. There's absolutely no way to get good scrolling performance without reusing cells. The cost, it's not just the cost of allocating the cells, it's the cost of allocating the core animation backing stores for all the pieces in that cell. Pushing all those things across to the graphics card, it's all very expensive.
When you're reusing a cell, we want to try to avoid re-layout. A good example of this might be if you have a label, and you're putting a new value in it each time you reuse the cell, you probably don't want to cause, like, size to fit on that, which would change the size of that label each time.
Doing that will invalidate the Core Animation Backing Store, cause us to create a new one, just the same as creating a brand new label will. In this case, you probably want to try to create that label to be whatever size you need to display any row in your table view, and leave it that size so that we can just reuse that backing store.
And again, make sure these things are opaque. Even the small amounts of transparency have noticeable impacts on scrolling. Sometimes you can't avoid it, but try really hard to figure out ways to do your own, use custom images or whatever you need to give the appearance of translucency, but still have opaque views.
This is that performance session I mentioned. If you missed this, I really urge you to go take a look at this when it's posted. The Optimizing Performance in iPhones application, there was a lot of great tips and tricks there, as well as tools you can use to see where your apps are slowing down.
So the last thing we're going to talk about is how to insert and delete things. We've seen a lot of applications where when somebody needs to change what's in a table view, they call reload data. And while this works, it's a pretty expensive way to update the content in your table view.
We end up having to throw a lot of things away, recreate them. And again, performance is pretty limited on this device. And so as much as possible, we want to avoid that. So we've provided some APIs to allow you to just insert and delete rows directly in a table view, and not have to reload. So the first is insert rows at index path with row animation. First argument there is an array. So you can specify an arbitrary set of rows to be inserted, all with the same row animation.
There's a corresponding delete rows at index path, so you can specify an arbitrary set of rows to be deleted. And our table view supports sections, so we have a corresponding set of methods to insert entire sections and delete entire sections, again in an animated fashion. Now sometimes you need to do both an insert and a delete at the same time.
If you need to do that, we have some methods that you can use to wrap all these, begin updates and end updates. So you can call begin updates, insert and delete an arbitrary set of sections and rows, and then call end updates and all of those animations will happen at the same time.
And at the end of this, whether you call one of these methods directly to just insert a row, or you call begin updates and do a whole bunch of stuff and call end updates, at the end of this, the table view's state internally is already the final state of all those animations.
So what this means is, even while those animations are in flight, you can turn around and call additional methods to insert or delete more rows. They can even be the rows that are still animating in. So for example, a row could be animating in, you could turn around and tell it to delete it, and it'll animate on out, right from wherever it happens to be already.
So most of those methods take this table view row animation argument. There's five things you can specify there. The first is a fade. So for example, if you're animating in, that row will fade in as the other rows move out of the way. You can also specify one of the four cardinal directions to cause that row to slide. For example, if you pick the table view row animation right, and you're inserting, it'll animate in from the right. And if you're deleting, it'll animate out to the right.
So let's now look at how to do insertion/deletion in our recipes list. The one thing we haven't done in our recipes application is allow the user to add an ingredient. We want to do this a lot like the phone application does it, where when we go into editing mode on that ingredients list, we want to add a row at the bottom with a plus sign next to it that lets us add a new ingredient.
Can we get the demo machine? OK, so we're going to go into our Recipe Detail View Controller. We're going to override what happens when we go into editing mode. Provide our own implementation for set editing. And we're going to call begin updates, because we want to insert that row-- or insert or delete that row, depending on whether we're going into or out of editing mode. We want to call super set editing animated to make sure the table view updates properly, and any other state that needs to get set is reflected. And then we're just going to create that array representing the index path we're going to be manipulating.
So that's just an array with one index path, and that's an index path that specifies the one row past the end of the ingredients list in the ingredients section. And if we're going into editing mode, we want to tell the table view to insert rows at that index path, and we'll specify-- Row animation top, so it looks like it sort of slides out from the row above it. And when we're going out of editing mode, we just want to delete that row. Again, with row animation top, so it looks like it slides back up under the rows that are there. And then we want to call end updates to kick off that animation.
So now we've told the table view that we want to add a row there, but our model has to reflect this, because the table view is going to turn around and ask us for information about that newly inserted row. So the first thing we want to do is in our number of rows in section method, here in our ingredients section, when we're in editing mode, we want to return one additional row.
And in our cell for row at index path method, here's where we're populating the value of that ingredients cell, but now we're going to come through this for a row that actually isn't in that ingredients array. So we're going to do a check here to see if the row that we're asking for is within that ingredients list.
and only then will we get the name and amount out of the ingredient. If it's not, We're going to assume it's that last row there, and we're just going to set the cells text to add ingredient. Now we might be reusing this row, so we need to make sure we get that amount label and set its value to the empty string.
We also want there to be a plus sign to the left of that new row. So in our editing style for row at index path, here we've said that if it's in the ingredient section, we want to be delete, but we want to make one small change here. If the row that we're looking at is that last row, one past the end of the number of ingredients, we're going to specify the insert editing style.
So when we go into editing mode, great, we've got this extra row that appears when we go in and out a couple of times, so you can see how that animates. It sort of appears to slide out from the rows above it, and it's got the appropriate editing type.
We've got a reorder control on that, which is probably not what we want. So let's go back. And go to the can move row at index path and put a similar check in here. The row we're looking at is that one that represents the add ingredients. We'll just return no to indicate that row's not reorderable.
[Transcript missing]
Remember this first set of if blocks checks to see if the section is before or after the ingredients section. So if we get past that, we're definitely in the ingredients section, but we want to make one more check here. We want to see if the proposed destination index path is greater than that last row of the ingredients. And if that is, we want to just return that last row.
Okay, so now let's go into editing here. Notice we don't have the reorder control there, and if I try to pick up one of these other rows and drag it down here, it'll just open a gap right above the add ingredient row, so we can't drag anything down below that. So we now want to allow the user to select those rows in the Ingredients section so that they can edit those ingredients. So Table View by default doesn't let you select rows when you're in editing mode, but there's a method to override this.
We just need to set the property, allows selection during editing to yes. And then we need to make a couple of changes. The first is in our will select row and index path. This block of code says we don't allow ingredients to be selected, so when we're not in editing mode, we allow everything but ingredients to be selected. But we want to flip that when we're in editing mode.
So we'll create an if block, and if we are editing, will say if the section that we're looking at is not the ingredients section, deselect the row, return nil. Otherwise, return the index path. And then if we are not in editing, we'll do the code that was here before.
Finally, in our did select row at index path, we've never had to handle the ingredients section here before because we didn't allow selection, but now we do. So we'll add one more else clause here. We're in the Ingredients section. We'll create a new view controller, which is the Ingredient Detail View Controller. We'll assign its recipe. And then if it's one of those rows where there's already an ingredient displayed, will get that ingredient and set that ingredient on that view controller. Otherwise, there won't be an ingredient there, and this means we want to add a new one.
Okay, so let's go in here. Go into editing mode. Now we can click on this, cloves like, good, we can change this, maybe make this a half teaspoon of cloves if we'd like. Maybe we want to add an ingredient. I tried these. There's something that's clearly missing. So we'll try that. I really like bacon, so I'm going to do about a pound of that. Lovely. All right. And we're done. So we talked about a bunch of stuff about how to control the table view, do a lot of great customization of the table view.
This sample's available. I urge you to go download it, take a look at it, try to understand how we're doing everything, and then go out and build some really great user interfaces with table views. If you need more information, you can contact Derek Horn. He's the application framework evangelist. There's also documentation on the website. There's a lab for this immediately following this session in iPhone Lab A. It'll run until about 2 o'clock today.