Mastering Table Views

Session 128 WWDC 2010

Table views present list information in thousands of iPhone applications, from games to utilities. Users often want to add, remove and edit their list data. Come gain a deeper understanding of how to enable table view content to be edited, new content to be inserted and unwanted content to be deleted.

Jason Beaver: Welcome to the Mastering Table Views session.

My name is Jason Beaver.

I am an engineer on the iPhone Frameworks Team, and I will be joined in a little bit by Luke who will run us through some demos.

Well, we have an quick introduction.

This is going to be a relatively advanced session.

We're not going to cover any of the sort of basic things of loading the table view, how to get content into it, how to customize cells, things like that.

We're going to touch on a couple of issues that we haven't had a chance to really focus on over the past couple of years.

And hopefully it will allow you to sort of add a level of polish to your application that your users will really enjoy.

So we're going to cover a handful of things today.

We're going the talk about the update mechanism built into Table View that allows you to change the contents of the Table View without completely reloading it.

We're going to talk about how to combine this with an editing transition.

If you're familiar with the context application and you go into editing mode, you'll know that there's some rows that are added to allow you to add fields to the contact, and when you come back out, we delete those rows so that you don't have to see them.

And notice all those animations are synchronized.

We'll talk about how to do that.

We now have the iPad for you guys to deal with, so we're going to talk about some differences with regard to the table view between iPhone and iPad.

And we're going to talk about if you need to do some sort of background loading in your application, for example, like the YouTube application loads, thumbnails in the background, we'll talk about how you do that with Table View.

We're not going to talk about background loading itself, but how that relates to the table view.

And finally, we're going the talk about using the new UIGestureRecognizerTechnology that was introduced in 3.2 with table views and how you can do some neat things to allow users to interact with your table views.

All right.

So let's jump into updating the contents of your table view.

So first of all, why do we want to do this?

Why not just reload the table view every time?

Well, there's one reason, it's improves performance, it's expensive, or somewhat expensive to completely reload a table view.

And these are limited performance devices, so just replacing the content that's changing will make the app more performant.

But the bigger reason is that it's easier for the user to understand what just changed.

There's some sort of context shift in your application, and an animated interface helps the user track what happened.

We're going to look at a couple of simple examples here where in the one on the left, we're just going to reload the table view, and you'll see the content just changed.

It was rather jarring.

The user has to look at it for a moment and sort of try to figure out what's old and what's new, and typically, in your case, it's not going to say "new," so that'll a little harder for the user to figure out.

But in the example on the right, now we're going to do this in an animated fashion, and when you walk in, you can see a set of rows were deleted, you see which rows were inserted.

It was a lot easier for the user to understand what just changed.

[PAUSE] Table View provides a mechanism to update individual rows as well as entire sections.

The methods to do individual rows are insertRowsAtIndexPath withRowAnimation, and similar deleteRows and reloadRows.

There's a corresponding set of methods to do the same sort of operations on entire sections.

Those are insertSections withRowAnimation, deleteSections and reloadSections.

So a high level, the way it needs to work in your application is, you have a model, it has some set of objects in it that you want displayed to the table view.

You have a data source, which is there to bind your model to the table view, and then you have a table view with one cell for each of the objects in your model that needs to be displayed.

And the way you handle this update is you first have to update your model.

So in this case, we're going to delete a row.

So you'll do that, your model will get updated, so now your model just has 4 rows.

And then subsequently, you need to tell the table view about the change to your model.

At this point, they're sort of out synch, and if the table view asks for something that might not be in your not be in your model.

So you tell the table, "Yo, hey, we deleted some row."

The table view will then take care of the deleting the road and animating all of the other rows in place to now put the table view in synch with your model.

But sometimes it's not as simple as just a single row insertion or deletion.

You have a bunch of changes to make.

Well, Table View supports batching these.

It all you need to do to batch is to wrap all of those update methods in calls to begin and end updates.

So let's go back to sort of our high level view of this.

We've got a model with some objects, and again, the data source and Table View.

And now we're going to, you know, delete an object, and then we'll maybe insert one at a different index.

And now we need to tell the table view about all those changes.

So we will message it a table view, we'll set it to begin updates, and then we'll tell it about the rows we've deleted, we'll tell it about the rows we've inserted.

And then we'll call endUpdates.

At that point, the table view will go to the model and try to figure out what the new state is and then perform the update.

In this case, we'll see that the row that was in Index 2, right in the center, will be deleted, a new one will get inserted at Index 1, and the one that was formerly at Index 1 will actually animate to its new spot in the table view.

The table view will take care of that automatically.

So there's a couple of important points.

One is that the order inside that update block doesn't matter.

The order that you specify the inserts versus deletes or even the order of the inserts if you needed a multiple inserts, you can specify those in any order within the update block and will treat them all as a group.

We'll go into a little more detail here in a moment.

So, here's a simple example where we're going to insert a couple of rows, and although we could have specified these in a single array,.

for example, we split this out into two arrays.

We're going to call it beginUpdates.

We'll make a couple of calls to insert rows at index paths, and then we'll endUpdates.

And the important to this is here that I can reverse the order of these two arguments; the table view is going to do exactly the same thing.

The second is that the table view state when you're doing batched updates is not updated until the call to endUpdates.

So when you make that call to insert a row or delete a row, your model doesn't have to necessarily have already reflected that.

Basically, the model has to be changed, though, by the time you make the call to endUpdates.

So, now let's talk a little bit about the first argument to those various methods, how we actually specify what gets inserted or deleted.

We'll start with deletes.

Deletes simply specify all of the current rows in the table view which should be removed.

So here, for example, we have a table view with seven rows.

We want to move to a new state.

If you notice, there's Rows 1, 3 and 5 are missing.

All we need to do is tell the table view "delete Rows 1, 3 and 5" because those are the indexes in the current state of the table view.

And all of the remaining rows will just automatically animate to their new spots.

Conversely, inserts specify all of the rows which are new.

So here, we're going to start with our table view in the same state it was at the end of the previous animation.

We want to move back to sort of the original state.

In this case, Rows 1, 3 and 5 are the new ones.

So all we need to do is tell the table view "insert 1, 3 and 5."

And, again, we can do that in any order.

We can say "insert 5, insert 1," as long as they're all in an update block, we'll treat those in exactly the same way.

And just like before, all of the remaining rows will animate into position.

Reloads are specified just like deletes.

They specify all of the current rows which need to be reloaded.

Here, we're going to start with our table view at 7 rows, we want to reload Rows 1, 3 and 5.

We just tell the table view "reload Rows 1, 3 and 5."

And just like before, the remaining rows animate.

On this case, there's not much to animate.

But the more interesting thing is what happens when you combine them.

This is the area that we've seen a lot of people get into trouble with, and they get confused about what exact indexes they need to pass.

But as long as you follow those basic rules, it all works out.

So here, we're going to start with our table view in exactly the same state.

We're going to move to a new state.

And if you notice, there is some a few rows that are missing, and there's a couple of new rows, there's a reloaded row.

So how do we actually specify what changed here?

Well, the first thing to know that in the new state of the table view, Rows 1 and 5 are gone.

So we need to tell the table view "delete Rows 1 and 5."

In the new state of the table view, Rows 2 and 3 are new, so we need to table view, "Hey, you can insert Rows 2 and 3."

And finally, 4 was reloaded and notice that its index changed in the new state, but we don't care about that.

We specify what load or reload in the former state of the table view.

And just like before, all the other rows will take care of animating where they need to to make room for these new rows.

So let's talk a little bit about what happens under the covers.

You can understand why sometimes things fail, and you'll get notifications of that.

The first thing that happens is a sanity check.

When that endUpdates call comes in, or even if you just do one of the individual insert, delete or reload by itself outside of an update block, we're going to go through and just sanity check it.

Well, the first thing we're going to do is we're going to eliminate redundant updates.

What I mean by redundant updates are if you've told us to insert an entire section, and you also tell us to insert a row within that section, well, that's implicit because that's part of the operation of inserting the entire section.

It means insert all the rows in that section.

And so, we'll go ahead and just take out the individual row insertion.

We'll do a similar thing on Deletes and a similar thing on Reloads.

So what we're sort of left with is just sort of the essential changes that need to be made.

The next thing we're going to do is call your data source, and we're going to ask for the number of sections in the table view and then the number of rows in each of those sections.

And then finally, we're going to verify that versus what we computed.

So if the former state of the table view has 10 rows and you say it tell us to insert a couple of rows and maybe delete one row.

When we ask you for the number of rows, you better tell us "11," right, because it's if you tell us any other number, we can't figure out how to get from the old state of the table view to the new state of the table view.

The next thing we do is actually rebuild the table view's geometry.

We do this by recalling the delegate methods to figure out all of the row heights, all of the header and footer heights, things like that.

And we'll use that to animate to the new position.

One subtlety here that's kind of interesting is that you can actually return different heights here, and we'll take care of animating those new heights.

So if you want to just change your the height of the rows in your table view, it's an easy way to do it.

You can do that in conjunction with a change of the rows in your table view.

You can also actually just do a simple Empty Update Block.

You can call it beginUpdates immediately followed by endUpdates with no actual changes to your table view.

We'll go through all the same steps here.

The whole first sanity check bit won't really do anything because you haven't told us any changes, but we'll still rebuild and animate the geometry, which means if you simply want to rechange the row heights in your table view, you can do that with an Empty Update Block.

An example where we use this is if you look at the S and S application and go into or out of editing mode, you'll notice all of the rows get a little bit taller as they gain a separator.

When they're in any mode, that's exactly how we do this.

So that's the first argument to all of those various update methods.

The second argument to all of those is a row animation argument, and that let's you specify the animation style for the row that's getting updated.

So that's done with an [inaudible] UITableViewRowAnimation.

And there's a handful we're going to talk about here.

The first one is the fade animation.

The fade animation simply makes the new cell that's going to be implemented fade in and out as the other rows slide in or out of the way.

So we're going to insert a row here right above where it says "Section 0, Row 3."

And we'll see, if you look at it carefully, you see that that row faded in, and now as it's deleted, you'll see it fade out.

We also support sliding the row that's being inserted or deleted in one of the four cardinal directions.

So for right, for example, in the same spot, we'll see a row slide in from the right, and when it's deleted, slide back out to the right.

We support a similar thing for left.

You can see it slide in from the left and then again back out to the left.

And notice it's also fading as it's doing this.

We also support sliding to the top and bottom.

If you watch it from the top, it'll look like the row will slide out from underneath the row above it, and then on the delete, will slide back underneath that row.

And then for Bottom, it looks like the row will just slide out from underneath the row below it, and, again, slide back underneath there.

So you have quite a lot of flexibility to sort of make things move in the direction that you need in your application.

You can also specify None.

Now, some people have the mistaken perception that this means there's not going to be any animations in the entire update block.

Again, this just specifies how the rows that are being inserted, deleted or reloaded animate.

The remaining rows will always slide to their new position.

So here, we'll see that same row.

Notice it's already fully opaque.

It's fixed in position.

It's not animating.

The remaining rows still animate around it, though.

Finally, in 3.2, we added a new style called Middle.

Middle will attempt to keep the cell that's being inserted or deleted centered in that gap that's opening up as we're inserting or deleting a cell.

And in the plain-style table view, it'll also add some shadows there and sort of give the illusion that that cell the behind the other rows.

So if we watch that here, you can sort of see there's some shadows showing and covering up that cell, and as it deletes, we run those again.

A couple of things to mention, we don't expect that you should use all of these styles in all circumstances.

For example, in Group Style Table Views, each section is surrounded by a rounded rectangle.

Right, if you're familiar with settings application, you know what this looks like.

And we want to give the illusion that that's an unbroken, you know, rectangle around all of those cells as you insert and delete things.

So if you use the left and right animation styles, obviously, you sort of break that line.

So we don't really anticipate you to use those, although it will work.

So top and bottom or middle are generally better for that.

The middle in the group style case actually will not display those shadows, because, again, it sort of looks funny with that rounded rectangle.

All right.

So that's how to actually to do the updates.

What if we want to combine it with an editing transition?

Well, all we need to do to do that is put the call to change the editing state inside that update block.

So here, we'll call to beginUpdates.

We'll do some amount of modifications to the table view, and then we'll simply call setEditing with whatever the new editing state is and then the call to endUpdates.

We actually will do the transition in the call to endUpdates.

It won't actually happen immediately when you make the call, and that'll be the point that we re-query for the editing styles for all of the new rows that have come in.

So now, let's bring Luke up and take a quick demo of what we've seen so far.

[ Applause ]

Luke the Hiesterman: All right.

Thanks, Jason.

I'd like to follow-up on all that information Jason just gave you by giving you an example of an application that can be greatly enhanced and user experience made much smoother by using animated updates to table view.

So the application that I'm going to give you an example of is very simple RSS Reader using the table view where each section of the RSS Reader represents an RSS Feed, and we have them one of them open at a time.

So I'll begin by giving you an overview of the application architecture.

We, of course, have an application and that application has an application delegate, and that application delegate displays a table view controller.

That table view controller has a data source, of course, as you're used to, which, for the purposes of this demo, will be opaque, and we won't really talk about how that works.

I assume you know how to control a model and put data into your table view.

So that table view controller naturally has a table view, and that looks like this.

And that table view has a custom section have a view that you can see there, and it also has custom cells.

Now, most of this stuff, we're not really going to talk about today.

What we're going to focus on is the table view controller and what you can do in that table view controller to customize your table view's behavior, specifically using animated updates and making things flow better for the user.

OK. So let's first look at the application actually running.

And this is my RSS reader.

Now, if I tap on this little button here on the side, I can actually open a section.

One thing you'll notice is that section snapped to its new open stage, so these cells just appear.

Likewise, if I close it, they just suddenly disappear.

I can open it again, open a new section.

And you see the table view just immediately snaps to the new state with the new cells.

So what I'd like to do is open these sections and close them and change sections in an animated fashion so that the user really sees what's going on, and everything just looks much more smooth and polished.

So if we go to the code, I have two methods that are going to be important here.

These handle opening and closing the sections.

They are Section Header, Section Opened and conversely, Section Closed.

And right now, they're both very simple.

All they do is update my model and then they call Reload Data, and that pops us to a new state.

But what we want to do is get rid of this Reload Data because that's what's causing us to pop to the new state because we're just reloading the entire table view.

Instead, we want to use animated updates to get to the new state.

So let's just start over, and we'll start seeing what we have to do to use animated updates.

So the first thing we need to do is figure out what are the rows that we want to insert, what colors to insert, index paths to insert?

And so we query and get that, and then we figure out what are index paths to delete?

OK. So now we know the rows that we're going to get rid of which are whatever rows in the currently open section and what rows we're going to insert.

The next parameter, of course, to the insert and delete rows methods is an animation type.

So we have an Insert Animation and a Delete Animation.

Now, generally, when you're deleting rows and inserting rows at the same time, if you're going to use a cardinal direction animation, you want to use opposite ones.

If you're inserting from the left, say, you want to delete to the right; that way you get a flow to the inserts and deletes and there's a sort of a velocity effect.

So here, I'm using top and bottom.

I'm inserting from the top and likewise, I want to delete out the bottom.

So everything is going to sort of flow down the screen, and there'll be a velocity that way.

OK. So I have my IndexPaths that I want to insert, I have my IndexPaths I want to delete, I have animation styles, I'm just about ready to do my update block.

Of course, before I do that, I want to actually update my model.

So I do that.

Of course, do this before the update block or at least before the call to endUpates, is the important thing.

And so now I do beginUpdates and I'm going to send in the index paths I want to insert.

I do that with insertRowsAtIndexPaths:withRowAnimation using the row animation that I determined before.

And likewise, I want to deleteRowsAtIndexPath.

And that's the end of my update block.

OK. So, that's the that's all the code that I need for my section opened method.

I just need to mirror some of this for my section closed method that will only have the logic for index paths to delete because this is what happens when I tap on the section that's already opened, and all I need to do is close it.

So once again, I will just compute my index paths to delete, go ahead and update my model, and this time I'm just going to call self.tableView DeleteRowsAtIndexPaths without an update block.

I could use an update block, and it would behave exactly the same, but in this case, I only have one action to do, so there's no batch operation happening, and that call will do everything I need right there.

OK. So let's run this, and see what we've got.

When I tap on my button here to open the section now, notice the rows smoothly animate into place, and there's not the snapping sudden action that happened when I used Reload Data.

Likewise, when I close the section, the rows smoothly animate out of place.

There is one caveat to this current implementation.

You'll notice if I open this other section while Hot News is open, the animation looks rather odd.

Remember, I said before that using top animation for insert and bottom animation for delete causes this sort of downward motion effect in the animation of the cells.

Well, that's fine, and that even looks OK if I open Hot News right now.

But this doesn't look good in this case when I open the section that's below the currently opened section because the section header appears to animate upwards.

In fact, it does animate upwards.

So, sort of the moral of this story is sometimes we need to put some thought into exactly what row animation styles we need, and I'm going to do that here by actually conditionalizing the row animation styles I use based on what section I'm opening relative to the currently open section.

So I'll just get rid of this.

[PAUSE] And I'm going to use that same paradigm as before, inserting from the top and deleting from the bottom only if I'm opening the first section or if the section that I'm opening is above the currently open section.

But in the other case, when the new section that's being opened is below, like we saw before where the animation was weird, I'm going to flip those animation parameters.

So now, I will insert with bottom animation and delete with top animation.

This will cause all the rows to appear to animate upwards, which will flow smoothly with the section header.

So, let's give this one more shot.

OK. That looks good.

Now what happens when I open this section.

The whole thing seems to animate upwards, and everything looks smooth and the same thing happens going this way.

So now I've successfully used Table View Animations to add a significant level of polish to my application with just a little bit of code that got away from using reload data.

And more than that, my user as significant contextual information that they didn't used to have.

So, that's using animated updates and UITableView and I'll hand it the floor back to Jason.

Thank you very much.

[ Applause ]

OK. Thank you again, Jason.

So, as Jason said, there are lots of views in the table view that are potential fodder for gesture recognizers.

The table view itself is a view, the section headers and the section footers are all views, the cells are views, and, of course, the table view has a table view header and a table view for themselves.

And these are all potential fodder for gestures, so there's lots of interesting things you can potentially do, lots of places you can do gesture recognition.

I'd like to take a look at the RSS Reader app that we developed in the last demo and look at three ways we can add gestures to views in that application to enhance the user experience end product.

So the first one we're going to look at is a pinch gesture recognizer.

That's not actually the first, but that will that's one that will be attached to the table view itself.

We're also going to look at tap gesture recognizer that will attach to the section headers.

And finally, we'll look at a long press gesture recognizer that we will put on the cells themselves.

So we've got one that will go on the table view, one that go on the section headers, and one that will go on the cells.

So all of the views in our app we're going to be using for gesture recognizers.

OK. So to the code.

The first one we want to implement is a tap gesture recognizer.

And the reason we're going to do that is if we run our app again, we can tap on this little button here, and that opens our sections and that's great.

But if these are misses, this relatively small button or taps anywhere over here, the touch is wasted, and it doesn't do anything.

We've got all this wasted space that we could be using for touch handling.

So I want to add a tap that will be handled anywhere in the Section Header View that will do the same thing as the button does now.

So we'll see just how easy it is to do that.

I'm going to go to the code for my section header and it has an ended method.

And in that ended method, I'm simply going do set up a UI Tap Gesture Recognizer.

So I do that by [inaudible] and I give it the gesture recognizers use a target action pattern, so I give it the target of Self and the selectors toggle open which is actually the same method that I used as the action for the button that is currently used in the Table View Section Header, which means I don't have to rewrite that code at all.

I already have all the code that handles the opening and closing of the section headers, and I'll just give it the same action to the gesture recognizer, and I'm done.

So all I have to do now is attach this gesture recognizer to the that went a little oddly.

Oh, OK. Excuse me.

I inserted that in a bad spot.

OK. So now I just need to add the gesture recognizer to the view which is the Section Header View, and I can just release it.

And with those three lines of code, I can now run my project.

And now I can tap anywhere in the section header, and it works just as before.

That easy.

[ Applause ]

Thank you.

OK. So the second one I want to look at is a pinch gesture recognizer on the table view.

And why do I want to do this?

I want to do this because when you look at this application, it's very easy to see that there's a lot more text that can be displayed in these cells than fits at the current size.

I could just blow up all the cell heights and have huge rows in my table.

I don't really want to do that.

What I want to do is let the user decide if they want to make a cell bigger.

And I'm going to have them do that by actually pinching the table view and resizing rows in the table view.

So, let's see how we can do that.

Let me go to the interface of my table view, and I'm actually going to add a couple of ivars so that we'll use for pinching.

And those are pinched index path and initial index path.

So when I when the user starts a pinch, I'm going to store that index path.

That's going to be the pinched index path so I know which row they're actually resizing.

And I'm also going to store the initial height of that row when they start pinching, and I'm going to use that to compute how big I should make the cell as they pinch.

OK. So I got my ivars in place.

I'm going to go to the init method from my table view controller.

I'm going to set up a pinch gesture recognizer just like I set up the tap gesture recognizer in the section header.

So once again, this is an [inaudible] and I end it with target of self, which is the table view controller.

And this time I give it a handle pinch as the action, which I obviously didn't have any resizing of rows functionality before, so this is a new method that I'm going to have to write in a second.

But then just as before, I add the gesture to the view, which, in this case, is the table view, and then I can release it.

And with those three lines, I've added a pinch gesture recognizer to the table view.

But I need to go implement handle pinch so that pinching does something.

All right.

Let's do that.

So hopefully you went to the gesture recognizer sessions yesterday.

But if not, as a quick introduction, gesture recognizers fire their actions and they can have one of several states from possible, began, changed, cancelled and ended.

And we're going to deal with a few of those in our action method, Handle Pinch.

So this is the outline of the method, and we're going to do something for when the gesture begins and UIGestureRecognizer state began, and we're going to handle the changed state which we get constant change updates as the user continues to pinch inward or outward.

And we'll also clean out any cancelled or ended state.

So let's start with the began state.

First thing we want to know is where the user's pinching.

We can get this from a property in UIGestureRecognizer that is location end view.

So we get that from the gesture recognizer, and we can use that point to ask the table view what self, what index path is at this point?

So we call self.TableViewIndexPathForRow and give it the pinch location that we got from the gesture recognizer.

All right.

So now we can set our initial pinch height, which we do by querying our model for what is the height of this row now?

We have the row.

We ask our model what is the height of it?

And we'll just save that off as our initial pinch height so we can use it for calculating changes in the pinch height later.

The last thing we need to do is call a method that we'll write in a second, which is called Update Pinch For Scale and send it in index path.

The first parameter is scale we get from the gesture recognizer, and that represents how much the user has pinched where 1.0 is the value when the user puts their two fingers on the screen, and it gets bigger as they pinch outward and smaller as they pinch inward.

So we'll write that method in a second.

We deal with the changed state by simply doing the same thing we did at the end of the began state and calling our update method, Update Pinch For Scale.

And when the gesture cancels or ends, we just have the clean up after ourselves by releasing our pinched index path.

So, all the work happens in Update Pinch For Scale.

And let's write that now.

So first thing we do is simply validate the index path that was sent.

And then after that, we need to do some math.

And we'll do that using our initial pinch height value and the value of the scale that we get from the gesture recognizer.

So we take the initial pinch height and we multiply it by scale, and that's how we get our new height, and we also pin it to a default minimum height, which we call Default Row Height, and that's the height of the row that you see in the application as we run it now, because we don't rows getting unreasonably small.

So we have the new height.

We need to update our model with that new height, so we do that with I call the self-set height for row.

And now that we've updated our model, we can take advantage of what Jason said earlier which is that using an empty update block, we can actually change the geometry of our table view.

We've already changed the geometry of our model and now we just need to use an empty update block, beginUpdates immediately followed by endUpdates and that will cause the table view to animate to that to the new row heights.

So we call beginUpdates and endUpdates and that's it.

Now, there's one problem with this implementation, and that is we don't really want to animate to the new positions, and that's because the gesture recognizer gives us constant updates as we pinch.

So table view, of course, automatically and by default animates all the cells to their new positions when we do an update block.

But, we want to disable that.

We can disable UIView animations with a class method on UIView, and we'll do that first by finding the current state of animations enabled, and then we can set Animations Enabled to No.

And because we're a good citizen, we're going to restore Animations Enabled to the value it was before this animation block.

So by doing that, by wrapping our animation block or rather our update block in Animations Enabled No, that tells us when we go to the new row heights, that we've specified and that we've put into our model, we don't want the table view to actually animate.

That'll just it'll go right to that, and that's fine because we'll be doing this very quickly and often as the user changes their pinch.

So let's run this.

And now, I can actually pinch if I want to relieve read more about Safari 5.

And as I expand my fingers or my virtual fingers, the height of the row expands.

And likewise, I can bring it back, and with a pinch, the user can now change the height of the row.

That's awesome!

[ Applause ]

Thanks. So there's one other way that we can go at the same idea.

Maybe we want to use the pinch instead to resize an individual row.

Maybe we want to act as sort of a zoom on the entire table view and resize all the rows.

So that's a pretty simple code change.

Right now, the row heights are specified in our model individually.

We have a value in the model for every row's height.

And we can actually just override that by adding a new ivar that we'll call Row Heights.

And this will override our current model and just set all row heights, all the heights of every row to the value that we have in our ivar row heights.

So let's take this, and we'll initialize it, and I'll end it with Style Method to our default value.

And here, I've got a couple of methods that handle my model for setting height and getting a height for a row.

And these have some kind of scary looking code that sets each of the individual row heights in an array.

I'm just going to get rid of that and make it much simpler so when I set Height, all I'm going to set is the height of my new ivar.

And likewise, when I query for the height, I'm just going to turn my new ivar.

And I'm not going to run it again.

We're going give an entirely other effect.

Now when I pinch, every row increases its height.

And I can even go.

And now the user can increase the size of every row with a simple pinch.

That's pretty cool.

[ Applause ]

OK. Let's look at one last gesture, and that's going to be a long pressed gesture.

Let's take a look at the app again.

What I want to do is actually add a long pressed gesture recognizer to the table view cell so that the user can tap and hold on a cell and will pop up a menu that allows them to e-mail a story to one of their friends.

So, we're going to go to everyone's favorite method, TableViewCellForRowatIndexPath.

And when we create a new cell, we're going to attach a gesture recognizer to it.

So we're going to use the same three lines you're familiar with by now in creating a gesture recognizer and attaching it to the view.

This one's a long press, and we give it target Self and our action, in this case is Hand the Long Press, and we add it to the cell this time.

The entire thing, as you see, is enclosed in a check to see if this device actually can send mail.

And now I need to implement long press.

So with Handle Pinch, we had to deal with several different states; began, changed and ended.

With the long press, all we really care about is the began state because that's we've begun the long press when the user has tapped and held on something long enough for it to become a long press.

So all we need to do in this case is very similar to the pinch gesture recognizer, we get the location in the table view and we get that from the gesture recognizer.

And then, again, we query the table view for the index path at that point.

And now we're just going to fire off a method that sets up an e-mail menu item for that index path.

And we can see that run now.

We press and hold, and now we get an e-mail menu item.

So that's it's another way we can add a gesture recognizer to another view.

In this case, the cell and gives the user another way to interact with our table view.

So, we've added three ways for the user to interact with the table view, and we've done that by gesture recognizers on three different parts of the table view.

We, again we added the tap gesture to the Section Header View, and that gave the user much greater ability to tap anywhere in the section, and it's much more difficult for them to miss their target.

We added a pinch gesture that, of course, gave them a great new way to interact with the table view in resizing row heights.

And we also gave them an ability to send an e-mail simply by long pressing on a table view cell, tapping and holding, and we've added all this great interaction with just a little bit of code and gesture recognizers in the table view.

So I hope that gives you an idea of how you can use gesture recognizers in your table view.

And thank you for checking out these examples, and I'll hand the floor to Jason for a conclusion.

Thanks.

[ Applause ]

Jason: Thanks, Luke.

Well, if you've never tried to add support like that to an app before, you have no idea how much of a nightmare it is to try to add some sort of a complex gesture recognition on top of something that already has a bunch of defined behavior.

We did all of that without breaking anything what table view does normally scrolling, selecting any of that kind of stuff.

If you tried to do this the old way with touch handling, it's really easy to just break some bit other functionality in the amount of state that you need to track is really quite huge.

So if you have any more information, our Application Frameworks Evangelist is Bill Dudney.

His contact info's up here.

There's a bunch of documentation on the table view and there's some great forums where a bunch of us participate, and there's a lot of other engineers like you guys who can help each other answer questions.

We referred yesterday or to the sessions yesterday on gesture recognizers.

If you didn't see them, I highly encourage you to take a look at them.

Some great content there.

So, in summary, definitely use animated updates to your table views and play around with gesture recognizers.

See if you can come up with some interesting ways for users to interact with your content.

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US