iPhone • 50:36
Table views present list information in thousands of iPhone applications, from games to utilities. Well-designed table views are critical to a responsive and effective user interface. Find out how to build yours to achieve a unique look and feel, maximize scrolling speed, and minimize memory consumption.
Speaker: Jason Beaver
Unlisted on Apple Developer site
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Welcome to the perfecting your iPhone Table Views Session. My name is Jason Beaver. I'm an engineer on the iPhone Frameworks Team. This session assumes you have a basic working knowledge of the Table View. We are going to cover-- do a quick overview. But for the most part, I'm going to expect the people here to know how to get some content into a Table View and really going to move into some of the more advanced content.
So here's what we're going to cover today. We're going to start with a quick overview, just so everyone's sort of on the same page about what a Table View is and what it can do, and then we're going to start talking about some of the new stuff in iPhone OS 3.0.
Then we're gonna delve rather deeply into cell structure. This is an area that I think there are some misunderstandings about and I really want people to understand what happens inside a cell, how the cells are put together. And then we're going to talk about cell design for some rather complex cells, several approaches you can take and advantages and disadvantages of each. And then we'll go into a fairly in-depth demo covering several basic approaches. And then we're going to cover some Gotchas, some areas that we've seen other people have problems with and hopefully you guys could avoid some of these issues after this session.
So let's start with the overview. Table Views are obviously designed to display lists of content. On iPhone OS, these are single column Table Views but support an arbitrary number of rows. They only support vertical scrolling, no horizontal scrolling, and they're designed for very large data sets. And as you'll see, these are very powerful objects and unlike Table Views on typical desktop platforms, these are used in virtually every iPhone application.
So here are a few examples of Table Views that appear in iPhone OS 3.0. And as you can see, Table View supports a very wide variety of display styles, even a few things that you wouldn't expect for Table Views like the SMS conversation on the left, or the Hawaii photos there on the right. Those are just Table Views with custom cells to render the content as appropriate.
Table View supports two basic display styles; the Plain style, where cell content extends from edge to edge of the Table View, and the Grouped style where sections are surrounded by rounded rectangles and the content is inset inside these rounded rectangles. There are a few basic components of a Table View.
There's an optional Table Header at the top of the content, and an optional Table Footer at the bottom of the content. And the content is separated into separate sections. And Sections have optional Section Headers and Section Footers, and then all of the data inside the Table View are separated into rows that are drawn by table cells. So these are what the components look like in a Plain style Table View, this is what they look like in a Grouped style, exact same components, just a different visual appearance. So that's a quick overview, and let's move in to what's new.
And we're going to start with your Table View itself. Prior to 3.0, we have the ability to insert and delete sections in an animated fashion. To this, we've added the ability to reload sections in an animated fashion. Similarly, we have the ability to insert and delete rows within a section in an animated fashion.
We've added the ability to reload rows within a section. Prior to 3.0, if you-- if your delegate received a message that the selection changed in the Table View, you were required to deselect the row. We've lifted that restriction in 3.0. Table View now does support persistent selections allowing you to build user interfaces like the voice mail, and the new voice memos in 3.0.
There's a new support class that sort of works alongside UITableView called UILocalizedIndexCollation. And this is designed to allow you to take your content and collate it or sort of bucketize it in a way that's appropriate for the user's current locale and then sort all of the objects within each bucket in a way that makes sense again for the user's current locale. So let's look at what this might look like for English.
Here are the objects that are in your model. Now if you ask UILocalizedIndexCollation for a set of the Sections that will be displayed in the Table View, in English, you'll get back the standard A through Z list, and a # at the bottom and you'll get back something else that's appropriate for the user's current locale.
But what's in that list really isn't important. Then for each object in your model, you can ask the LocalizedIndexCollation which section should this appear in. So for example, in English, Daniel Higgins would appear in the H section. And you'll do this for each object in your model until all of the objects have been placed within one of those sections. Then for each section, you're going to take all of the objects in that section and ask the LocalizedIndexCollation to sort those in a way that's appropriate for the current locale.
In this example, we only have one section that has more than one object, but when we sort that, it will appear in a way that's sorted appropriately for English. Once you've done this, you now have the data in a format that's suitable for display in the Table View, so you can load the Table View and the contents will appear there. So your Table View itself doesn't have a whole lot new, most of the meat of what's new in 3.0 is in UITableView cell.
The first is a new Designated Initializer. Prior to 3.0, when you created a cell, you used the initWithFrame, reuseIdentifier Initializer. The frame argument was always ignored, the position and size of the cell was defined by which row it was in and what the delegate specified as the height for that row. This has been replaced with the new initWithStyle reuseIdentifier. And let's jump in to those cell styles.
There are four of them supported in 3.0. The first is the Default Style. This gives you a stock Table View cell with the exact same display characteristics we had prior in 2.0. The next is the Subtitle Style. This is the style we use in the iPod App for music and albums and things like that. And this supports a title at the top and a subtitle below and an optional image on the left hand side.
There are two new Value Styles, the Value1 and Value2 Styles. These are intended only to use currently in Grouped style Table Views. And the Value1 is the same cell style we use in the settings application and the Value2 Style is the same style we use in the phone and contacts application.
[ Pause ]
Excuse me.
If you want to set the image or selected image on a cell prior to 3.0, you use the Image in Selected Image Properties. These have been deprecated in 3.0 and replaced with direct access to the Image View that's used to display those images. So if you had code that just set the image on the cell, this would be replaced with code that set the image on the Image View in the Table Cell. Similarly, if you had set the selected Image 4, this would be replaced by setting the Image View's Highlighted Image Property, and we'll talk a little bit about why this is Highlighted Image and not Selected Image in a moment.
Similarly, if you wanted to set the text and appropriate associated text attributes in a cell, we have a whole bunch of properties. These were largely just mirrored from the label. So in 3.0, we've deprecated these and just expose the label directly. This gives you not only access to these but the other attributes that are on the label.
So code that set the text on a cell replaced by code that sets the text on the text label, code to set the font would replaced by code that sets the font on the text label, similarly for all the ones. Again, with the exception of Selected Text Color which has been replaced with the text label's Highlighted Text Color. So these are the four styles. Let's talk about how you get access to the various pieces inside there.
First is the Image View. The Image View obviously gives you access to Image View that's used to display the image along the left hand side of the cell, both the Default and Subtitle styles support an image view if you ask for the Image View. For the Value1 and Value2 Styles, you'll get back nil because those styles don't support that. If you ask for the text label, you get the Primary Text Label, that's the obviously, only one in the default case, the top title and subtitle in the left label in both the value cases.
And we've added a new property called "detailTextLabel" that's used by the new styles, to support either the subtitle or the values on the right of the value styles. The Default style doesn't support detail as well, if you ask for the detailTextLabel for a Default style cell you'll also get nil back.
Prior to 3.0, we had a property called "hidesAccessoryWhenEditing" if you wanted the accessory to only appear when you're not in editing and move out of the way. When you went into editing, you could set this property. We've deprecated that as well in 3.0 and replaced it with 2 new properties; the editingAccessoryType and the editingAccessoryView.
So now, if you want to have the exact same accessory type when you're in editing and not in editing, you can just set them to the same type. If you want them to be different, you can set them to different types and we'll automatically animate and crossfade between them.
Or if you want it to appear in only one case and not in the other, you can set the case you want it to appear to the AccessoryType you want and the other one to None and we'll animate it in and out as appropriate.
Prior to 3.0, we had a Selected property on the cell. To this, we've added the Highlighted property. And let's look at what happens when we have a cell and selection happens. So the cell is the Multi-select Cell in Mail and normally, when the cell exists and it hasn't been touched, drawn in white and it's neither Highlighted nor Selected. When the user's finger comes down on the cell, we mark the cell as Highlighted and the cell gets a chance to redisplay itself, but it's not yet Selected.
When the user then lifts their finger, we will send a delegate method to select the row, and then the cell will move to the Selected state but it's no longer Highlighted because the finger isn't on it. The user then puts their finger back down on the cell, we'll go back to the Highlighted state but notice we're still in the Selected state, and then once they lift, will go back to the top state. Notice the cell can choose to draw itself slightly differently when it's Highlighted or Selected.
In 3.0, we've finally fixed Background Color for Grouped Style Table View Cells [applause] [laughs]. While technically correct before it was not what people sort of expected to happen, the cell is actually the area outside that rounded rectangle. And that rounded rectangle is drawn by the background view, but this is not what most people expected to have happen. And so, we've changed the sort of default behavior to now color the area inside that. Alright, so that's Table View Cell.
Let's move on to Table View Controller. In 3.0, when the Table View Controller gets a notification that the keyboard will appear, if the first responder is inside one of the cells, so if you have a text that's editing in the text field inside one of the cells, we'll automatically scroll that cell to be visible. So if that text [applause]-- excellent [laughs]. So there, you had to manage before.
So if that text field was in the region of the screen where the keyboard was going to appear, you had to sort of recognize that and move things around. There's a new Subclass of UITableViewController called "NSFetchedResultsController," and this is designed to work with Core Data to automatically get content from your Core Data database into a Table View. And there'll be a session I believe, this afternoon at 5 o'clock discussing this. There's also the Core Data Programming Guide as well as the Core Data Book Sample that you can take a look at. We're not going to have really time to cover that today.
[ Pause ]
So let's jump into Cell Structure. What we're going to show here is some sort of exploded diagrams of the various pieces in a cell, sort of-- as if they're sort of spread out in a Z order. We're not looking at the subview relationship as much as sort of the visual stacking of the various pieces and what sort of covers what.
So at the back of all this is the cell itself, that's the thing that's added to the Table View. And in front of that, if it exists, is the Background View. So if you want to have a custom background on any of your cells, you can just set a Background View and it'll be placed there in size to the size of your cells.
In front of that is the Content View. This is the place that you add all of your own Custom Views. I've drawn it here, sort of with a dotted line to indicate that it really doesn't contribute anything visually to the display of the cell. It's simply sort of a container for your own views. And then inside that are the various views that make up your cell. This is just a Default style cell.
So we have just an image view and a label. And when you go into Editing Mode, you can get optional editing controls on the left and reorder controls over on the right and we have accessories and things like that. Those also sit in front of the Background View. In this case, we have an Edit Control and an Accessory. And the content is just inset to take up the remaining space and then all the controls inside are adjusted to fit in what space is left by the Content View.
So now, what happens when we select the cell? Well, everything up to the Background View is exactly the same, but we insert a Selected Background View in front of that. That's also a property on the cell, just like Background View that you can set. So if you wanted just to have a custom Selection style, you can just create your own custom Selected Background View and we'll use that instead of our own. If you don't specify one, you'll get the default blue gradient that we see and that's adjusted as appropriate for a Grouped or Plain style.
And then in front of that is all the same views we saw before whether it's-- whether you're not in editing mode like here or you go into editing mode and you have some sort of edit control. Now, all of the samples and all of the code that we've talked about up to this point in prior sessions, we talked about how important it is for all of those views that you put in your Table View Cells, to be opaque for performance reasons.
This sort of presents a problem here, we've got this nice blue gradient, but all these opaque views sitting in front. Table View Cell actually handles all this automatically, but you sort of have to understand how to write your custom Views so that they work nicely in this system. So let's talk about what happens to these views in front when selection happens.
So at the point we're told to select a cell, we're going to save off three bits of state about every one of those views, regardless of how deep that view hierarchy is. The first is we're going to try to save off of that view is Highlighted. Now, not all views have a Highlighted property. So we're going to check to see if your view implements both the Set Highlighted method, and Is Highlighted method. And if it implements both of those, we assume it understands how to do highlighting, and we will call Is Highlighted and store off that state.
We'll then get the Opaque Flag, that's a property on UIView, and the Background Color, it's also a property on UIView, and we'll save those off. And then we're going to reset each of these three properties. Again, if you implement Set Highlighted, we're going to call Set Highlighted with a value of YES. We're going to set Opaque to NO, and we're going to set the backgroundColor to clear.
This allows us to see through to that selected background. On deselection, we're first going to start fading out that selectedBackgroundView. We're going to start an animation and take the alpha from 1 down to 0. And at the midway point of that animation, we're automatically going to crawl back through all of those views and reset the Highlighted state.
So whatever value we saved off before, we're going to put it back. And then at the end of the animation, we're going to again crawl back through all of those views, and we're going to reset the opaque flag, and the backgroundColor to what you had before the selection started. So let's visually look at what this looks like.
This is just a simple cell that has an added control on the left, an icon text and an accessory. And at the point we do the selection, we insert that Selected Background View, and sits behind all those other views. But at this point, the views are all still opaque; still have their same background colors.
So we're going to go through and remember all that state. And then we're going to set the backgroundColors to clear and set them to not be opaque, and then we're going to go back through and set them all to be highlighted. Obviously, you don't see all these intermediate states, we do this all in a single turn of the run loop. So visually, we go from this directly to the selected state.
Now when the deselection happens, we're going to start fading out that Background View. And here we've gotten to the midpoint, and we're going to go through and change the Highlighted state for all those views, this is how we toggle right at that midpoint, and then we'll continue fading out that Background View. So this is a really common area we've seen some mistakes.
People that-- instead of implementing Set Highlighted on their Views, look at the cell state. Well, the cell's Highlighted and Selected states change at a slightly different time than the views inside. And they won't change in the midpoint of this animation. So this is really the right way to get the same look that we get in all our cells.
[ Pause ]
Last year, in the Advance Table View Session, we covered sort of two different approaches when you have a cell. You can just create an instance if you like Table View Cell and just start adding subviews to the Content View, or you can subclass your UITableViewCell and sort of manage things in there. Really, in either case, you're adding subviews to the Content View, but it's really sort of who is responsible.
This year, we're going to look at it from sort of a different access. We're going to talk about adding Individual Subviews, things like Buttons and Labels and Image Views versus adding a Composite Subview, a view that draws all the individual pieces. Then hopefully, by looking at last year's session and this year's session, you can see sort of which one of these boxes sort of make sense for you and your application.
So let's start with Individual Subviews. Here, we have the YouTube cell. And YouTube cells are fairly complex cell. It has a big Image View, several Labels, a Rating View, and Accessory. And if we were to decompose this as Individual Subviews, we'd have something that looks like this. Various, you know, look what, four different Labels, a Rating View, and an Image View, all the subviews of the Content View. Now, this is really nice because it's first of all, easy to develop. You can mark this all up in Interface Builder.
You can go right in Interface Builder and move the pieces around. And it's also nice because as the cell changes size, for example, if you rotate the device to Landscape, you can set up appropriate autoresizing masks for all of these pieces they all change size and position, sort of automatically. Of course, the disadvantage is there's a lot of views here and it's pretty expensive to composite all these views, and you really notice this in scrolling performance.
[Clears throat] Now one of the ways to address that is to just have a single view. Here, if we were to redesign this to just have a single view that draws all the pieces, might look a little bit like this, you'd have the same Content View. And then inside that's a single view within its draw rack, just draws all of the various pieces that you see.
Now this is a little harder to develop. You have to spend a little time fine-tuning the code to position everything appropriately. It's a little harder to maintain because it's all in code. You can't just go into Interface Builder and adjust things. But it's really fast. In fact, almost regardless of the complexity of the cell, this is about constant speed scrolling because most of the cost of scrolling is recompositing as things move up the screen, not the cost of doing the first draw as the cell enters the screen. But this is, you know, very complex to handle things like cell size changes when you rotate, you have to redraw to handle that new width.
And having everything move in a nice animated fashion is really not easy to achieve. So they're sort of a hybrid approach. This works sometimes when you only have one or two pieces that need to move differently from the rest of the content. Again, if we look back to the Mail Cell in 3.0, we look at the animation that happens as we go into editing mode, pay attention to the main three Labels that are on the left versus the date on the right. You notice they move at slightly different rates.
If we look at this again, sort of in slow motion, you can see that the main three Labels have to move quite a bit more than the date does. So this is sort of a good candidate for drawing those three Labels on the left in a separate view, but then using a UILabel for that other piece that has to move a little bit differently. So we're going to jump into a demo now.
And we're going to look at 3 different ways to render the same content. Now, this is a content that's supposed to look just like the application store. We have alternating row colors with-- if you can see it there, little grooves between the cells, some nice images with shadows I think.
Labels, Rating View, things like that. And we obviously want the price that's, you know, over on the right to stay right aligned as we go in and out of Landscape. Let's take a quick look at this project. We have a Root View Controller and this thing is going to manage the Table View that we see.
And we have three-- well, first of all we have an Application Cell. This Application Cell is just an abstract superclass for the three subclasses that are going to use the different styles to display. This has a Boolean as whether this particular row should have a dark background or a light background. And then, properties for the various - Icon, Publisher things like that. I'm sorry. - I thought that was already set.
[ Pause ]
How's that? Good? [Applause] Alright, sorry about that. OK, and then like I said, we have three different subclasses. We have an individual Subview Based Application Cell and this uses a nib to have-- to display all the various pieces, and you see we've got IB Outlets for all the various views we're going to use to display the components. We have a composite Subview Based which has a single Content View, we use to display everything, and then we have the Hybrid Subview Case which breaks out that Price Label as a separate view. So let's start filling out the View Controller.
So we're going to start by setting the Background Color of the Table View to this dark background that we've defined. This is the area that's outside any cells. So if you pull the cells down, the area you see above that, or if you pull the cells up, the area below that, then we're going to set the separator style to none. We're actually going to draw that groove using the Background Views of the cell. And then finally, we're going to load the data. In this case, I'm just pulling the data from a plist that specifies all the various components for each row.
And now we need to implement the methods to get the data into the Table View. So the first is the number of sections in Table View. In this case, we just have one. The number of rows in this section will be the number of objects in the data that we loaded from the plist. And then the real meat of it is in tableView:cellForRowAtIndexPath. Well, like good citizens, we're going to use cell reuse. So we'll create a Cell Identifier. We're going to attempt to dequeue one of those cells and if we don't get one, then we're going to load our nib.
Now, we're going to be the owner of this nib and we have an instance variable called _cell that will point to the cell that gets loaded as a result of loading this nib. We'll assign that to the Local Cell variable and then just nil out our instance variable so we don't hang on to it.
Now, we need to set up the various attributes of this cell. The first is whether or not the cell needs to use a dark background. We'll just obviously, alternate this here. And then we'll get the appropriate data item from that data that we loaded. And we're going to set all of the various properties, Icon, Publisher Name, et cetera, just pulling those out of that dictionary.
[ Pause ]
We're also going to set the Accessory Type to the Disclosure Indicator, and then we'll go ahead and return the cell. There are a few more things we're going to do. In the willDisplayCell, we're going to set the cells' background color to either the dark background or light background depending on whether the cells use dark background property with set. It's another area of confusion that we've seen some people encounter. They try to set the cells' background color when they create the cell.
And unfortunately, that doesn't work because after that cell is returned to us, we need to set some additional parameters on the cell to make it fit nicely into the Table View, things like background color. So if you want to override some of these additional properties, you need to do it in the willDisplayCell:forRowAtIndexPath method.
This is called just before we put the cell on the screen and Table View will not touch the cell after this. So any changes you'd like to make will persist all the way to the screen. We'll implement the Did Select Row forRowAtIndexPath and simply deselect in this case.
Normally, you would perform some action but we won't really care here. And finally, we're going to implement the Should Autorotate to Interface Orientation View Controller Delegate method and indicate that we support portrait in both of the landscape orientations. Let me quickly jump over and show you this nib.
[ Pause ]
Again, the file's owner is our View Controller and we have a cell property that points to this cell. And as you can see, it has all the various components that you would expect to see there, and appropriate autoresizing masks so that things move around as appropriate. So now, let's go ahead and define this individual Subview Base Application Cell.
We need to overwrite Set Background Color because we want all of these views inside to look seamless and transparent against the background that's there. And we're going to set the Background Color in the willDisplayCell to either the dark or light color. And so we need to sort of forward that on to all the views inside the cell. We next need to override each of the setters the Application Cell defines for things like Icon, Publisher, et cetera to forward those on to the views that we're using to display that content.
And then, let me show one last thing, which is how we're doing the actual background itself. On application cell itself, when you call Set Use Dark Background, we're going to go through and create a UIImageView to display an image that's either that dark or light background with the little grooves at the top and bottom.
And we're going to that by loading an image, and then we're going to create a stretchable image, and I'll show you the image in just a second here, and then we're going to put that image in an Image View and specify that that has flexible width and height, and then set that as the Background View.
[ Pause ]
So this is the dark background. Let me zoom way in so you can see this here. It's actually only a 3 pixel tall image. We use the stretchable image property of UIImage to create an image that causes the entire width of this image to stretch across the cell, but just the middle pixel to stretch vertically to fill any area that it needs to. And we have a similar light background with some lighter colors. So with just these changes, let me wash this with instruments. So we can take a look at the frame rate we get while scrolling this.
Alright, before I jump over and actually show you visually how it feels, I'm just going to play around with this here and just take a look at the frame rate we get. Not a real great frame rate. Not horrible, but not great. Visually, if we look at it, it's not too bad, but it's a little jumpy.
[ Pause ]
Just let me show one other thing here. All of these views, because we're using stock Views like Labels and Image Views and things like that, already implement Set Highlighted. So when I select one of these, let me do it over here so you can see this, you see that all of the labels automatically switch to white, the Accessory View switches to white. And then as it fades out, they all toggle back at the intermediate point. So just because those views already implement Set Highlighted, we move to some of the more-- the views that we do ourselves will show how we can get that same effect.
[ Pause ]
[ Pause ]
So we'll override the designated initializer here, call super on that. And we'll create our own content view. This is going to be a Custom Subclass of UIView. We'll set a couple of properties on that, an autoresizing mask so that it fills whatever area in the content view it can. And we'll set the Content Mode to redraw, so that if it does change size, we're asked to redraw at that new size.
And then we'll add that in to the content view and return the cell. Just like in the individual Subview Based Application Cell, we need to override Set Background Color to forward that background color on to our Custom View. And then let's go on and define that Custom View now.
So we'll subclass UIView, create our very long named custom subclass and we'll have a back pointer to the cell so that we can get those attributes out, Icon Name, things like that. And we'll also have that highlighted property since we want to participate in highlighting the way other views do.
We'll define our designated initializer and then move on to the implementation. In this initializer, we'll obviously call super, and we'll hang on to that cell. We'll mark ourselves as opaque and set our background color to the same as the cell's background color, and then return our view. We need to implement Set Highlighted and Is Highlighted to participate in this highlighting mechanism, and we'll store that in our own Local Instance variable.
And notice under Set Highlighted, we'll mark ourselves as needing display because this view is drawn with a draw rack and when this state changes, we need to redisplay ourselves. And finally, we'll actually implement the draw rack. And this just contains a series of lines that draw the appropriate pieces.
Here, we're drawing the icon at some particular point. Depending on the state of our own highlighted property, we're going to set either the white color or black color and draw, in this case, the cell's name. And similarly, either set the white or a grayish color to draw the remaining publisher and number of ratings.
We want the price to stay right aligned, so we need to actually figure out how wide that is and then back up an appropriate amount from the right edge of this view. So I'll use the Size with Font Method to figure out how wide the price is, and then back up an appropriate amount and draw that string there. And then the last little bit, we're not going to jump into too much detail.
But basically, this draws the stars' background, those white stars that appear, and then some portion of the foreground of those stars. The sample will be made available later and you can look at exactly how we're doing this. So now, let's go over and tell our Root View Controller that we need to use this new subclass of cell, so let's comment out this old code -- oops.
[ Pause ]
[ Pause ]
Let's again launch with Core Animation so we can see what the frame rate of this is. OK. So I'll leave it here for just a second and sort of play with this and you can see what sort of frame rates we get.
Much better. In fact, it's capped at 60 and we're very much approaching 65, going a little bit over. And while we're here, let's go ahead and look at the selection. If I select this, notice that the exact same-- I moved, sorry. Notice the exact same visual appearance.
[ Inaudible Remark ]
I'm sorry.
[ Inaudible Remark ]
Yes, there you go. Notice the exact same visual appearance. Everything highlights, notice that it toggles at the midpoint appropriately.
[ Pause ]
So there's one other thing that I'll show. And I'm actually-- I'm just going to move over to the simulator to show this 'cause we really want to slow this down. 'Cause we have a problem here, we're just drawing everything. What happens when we rotate the device? This content can't smoothly adapt to the new width of the cell. So let's-- I'm sorry, let's switch this over to the simulator. We'll build, and then actually let's-- we don't need to run it in instruments here.
[ Pause ]
OK. Let me slow this down and watch what happens now when we rotate this content. Notice the content is all squished and then sort of stretches out to the right size. We go back the other way. It starts all stretched out, ends up at the right size.
What's going on here? Well, by default, there's a content stretch property that defines sort of what region of a cell can be stretched if you try to display the cell in a different size than the actual underlying backing store. So in this case, when we go to rotate, the cells are all asked to redraw at their new size, but then the frame animates from the old size to the new size.
So the content that's now drawn at that new smaller size, gets stretched out to fill the old size, and then gets animated back into position. So what can we do about this? Well, that content stretch property is something you can set yourself. You can define what region of a view is allowed to stretch. We'll notice that we have this area here between the ratings and the price that's empty. We can stretch that region in and out and we're just fine with that. So let's see how we'd do that.
[ Pause ]
So let's come down here and we're going to override Set Frame, because whenever the frame changes, we need to redefine what region of that is stretchable. We'll call super. And then an important bit here is we're going to set the content stretch to some rectangle, ignore the math for a little bit about how we define what part of that is but that's the area over there where we're not drawing any content.
Notice we've wrapped this in calls to set animation Enabled because Content Stretch is also animatable. And so if you-- the Content Stretch starts out as sort of a full thing where as it's changing size, you're defining this region that's stretchable. We'll try to animate the stretchable region and you'll get sort of ugly parts animated.
If you really want it to be fixed, turn off animation so Content Stretch will snap to the new position that you care about. So we'll build this [inaudible], oops, I put this in Root View Controller, I meant to put this over in the Composite Subview Based Application Cell. There we go.
So let's now take a look at this. Let me slow this down again. And notice now, we get nice stretching, the way we really wanted to see it. We'll go back again to Portrait. OK, so that's with the composite Subview Based Application Cell.
Let's now look under the Hybrid case. If for some reason that you didn't-- some reason you didn't have some region like that that's stretchable.
I mean, we got lucky sort of in this case because we have this area here between the Ratings and the Price that we don't care if it is stretched. But suppose you didn't have this, more like in the Mail case where that date overlaps some of the content. Well, in that case, the Hybrid approach is a great way to go here.
[ Pause ]
So we'll start just like we did in the Composite Case by overwriting the Designated Initializer. And we'll create a content view that in this case, draws everything but the price. This is basically exactly the same code we saw before. And now, we'll create a Price Label as a separate view in our cell. We'll set this to be Right Aligned, set the Font, Text Color, and Highlighted Text Color so that we participate in this highlighting nicely. And Autoresizing Masks so that it stays pinned to the right side.
And we'll add that to our content view as well, and return the cell. We also need to override Set Background Color just we like we did in the other cases to forward that Background Color on to the Subviews. And just like in the Individual Subview Based case, now when we receive the Set Price, that's stored in a separate view. So we need to override Set Price and call on to the Price Label to set that so that it appears.
Just like in the Composite Subview Based Application Cell, we also need to define that Custom View we're using and this is really almost exactly like the other one with the exception of the draw rack which doesn't draw-- which does not draw the price. Now let's go tell our Root View Controller to use this Custom Cell.
[ Pause ]
And let's go back over to the device and run this again in instruments so that we can see what the performance impact of that was.
[ Pause ]
Lovely.
[ Pause ]
OK. And notice if I play with this here, reading pretty good scrolling performance, definitely not quite as high as just drawing everything directly, but we have an extra view we're compositing each time. So it's definitely not bad scrolling performance, and this might be acceptable for the case that you have. Let's look at it over here. And I don't know if you're going to be able to see this here if I rotate. No, we'll have to go to the simulator.
[ Pause ]
Notice the price still stays right aligned just like it did in the Composite Case after we adjusted the Content Stretch. A little easier to do this and again, it would work automatically even if the contents were overlapped. And that's it for the demo. So let's conclude with some Gotchas.
[ Pause ]
There's a thing we've seen a few times. Begin and End Updates are methods that Table View implements that allow you to animate multiple things simultaneously. So for example, if you're inserting a cell and deleting a cell at the same time, or you want to both insert and delete some cells while you're transitioning into or out of editing mode. You would wrap all of those calls in a Begin and End Updates call and that causes us all to-- because this causes the Table View to animate all of those together.
We've seen a few cases were these were separated wildly in the code and were not called almost immediately back to back which is their intended use. If you called Begin Updates and don't call End Updates, the Table View is just going to sit there sort of waiting for you to tell us that you're done updating the Table View.
So if the user tries to scroll or you try to reload the Table View during that time, nothing is going to happen. So these need to be matched and typically are called around a fairly small block of code. This is another one I've-- that I see pretty commonly when I look at apps in the apps store.
When you-- if you ever select a cell, the set- the text all goes nicely white but the moment you lift, it all goes back to black instantly. It's usually because somebody's made this mistake and they're using the cell's Highlighted or Selected state to trigger whether to draw a black or white text, and not the view itself. Because remember, we toggled that at the halfway point of the animation, while the cell itself goes back to non-Selected immediately after we deselect the cell.
This is another one that hurt a number of applications in the move to 3.0. While in general, UIKit does support putting views outside your super views bounds, within Table View, things like cells and Header Views and things like that are all sort of butted up adjacent to each other. And if you draw outside any of those components, you're going to overhang some adjacent component. And we don't make any guarantees of the Z-ordering of any of these things. And so, there are cases where we found where people were drawing outside their Header View, for example.
And that was obscuring a row, and now we change the order and it doesn't anymore. In that case, the right answer would have just been to make Header View taller. So with inside Table View, keep the views inside their super views bounds and you won't have issues as we change things going forward.
We go to a great deal of trouble, not only in Table View, but really all over the iPhone OS, to give the appearance of this nice translucent interface where there are shadows everywhere and you can see through the different things. And in some sense this is largely an illusion.
We go to a great deal of trouble to sort of give the appearance of transparency, but transparency is very expensive to composite and these devices, although they look really fast, under the covers, aren't, as you probably know. So there are a lot of cases where people look at how something works and think "oh, well, the only way to make this work is to use some transparency there." Selection was an example of this.
Early on, before we put out some samples that showed the right way to do this, people saw that nice blue gradient and said, if I want that to appear behind my Labels, I've got to create transparent Labels and they would just create transparent Labels, and scrolling was abysmally slow.
Hopefully, some of the stuff we covered today that shows you some of the machinery inside the cell that takes care of some of this for you, will make it possible for you guys to write cells that are very high performance but still can participate and look really nice when you do selection.
So another one I alluded to earlier, there are certain cell properties that can't be set when you create the cell because after you return your cell from cellForRowAtIndexPath, we're going to set additional properties on it, things like the background color. So if you want to adjust those things, do that in the willDisplaycell:forRowAtIndexPath, and like I said, we won't touch it again before it makes it to the screen. Surprisingly, we still see people not reusing cells. This is so trivial to do-- you OK? [Laughter] Sorry. Distracted by somebody falling.
[ Inaudible Remark ]
You OK, sir? Alright. [Laughter] [Applause] So, as hopefully you've seen here and in countless other demos, cell reuse is just trivial. Please reuse cells. You really can not get good scrolling performance without doing so.
[ Pause ]
I think this comes from people who are used to NSTableView on the desktop where cells are sort of these transient things that are only used to render, but don't really exist after the fact.
On-- in UIKits, cells are really first class views. And so, if you want to change something on the screen, on NSTableView on the desktop, you are required to reload that row. And we'll go back and ask your Data Source for new cells and we'll draw the pieces. In UIKit, those cells still exist on the screen.
If it's on the screen, there is a cell and memory for that row and you can ask for it. So if you just need to change some value, like a Label that's currently on the screen, you can just ask the Table View for the cell for that row, reach in, get the Text Label, set the text, that will automatically trigger a redraw and will update the screen.
We do support things like reloading individual rows or all of the data, and this is really used not when you just want to change one thing, but when all of the content needs to change, or you want to animate something in some way like-- when if you want to reload a row and have the old ones sort of push out to the right while the new one slides in from the left, that's an appropriate use of the reload API.