Frameworks • iOS • 52:54
iOS 8 brings dynamic type front and foremost throughout UIKit classes. Learn about extensive enhancements made to table views and collection views, empowering you to create dynamically sized cells and exercise greater control over layouts. Find out how to align user expectations from their settings to your UI.
Speakers: Olivier Gutknecht, Luke Hiesterman
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Thanks so much for coming to "What's New in Table and Collection Views". This is a topic of special dearness to my heart as we give it every year. Well, not exactly at this session but something on collection and table views we usually to get to talk about.
And that's really because table views and collection views are a tool that get used in nearly every single application and we recognized that as we went through to add a lot of improvements to iOS 8. We really wanted to do things that made your life easier doing awesome things with the tools that, as I said, just about everybody uses.
So, what we're going to talk about today is a few fundamental improvements to both table view and collection view. And we'll be beginning with table view. In particular, the big thing that we're going to be talking about today is how to do Dynamic Type adoption in your table views.
We're going to be asking that all applications that deal with text in iOS 8 adopt Dynamic Type and you'll see in fact that all of our system apps have already done this. So users are going to expect that your application do this. And I'm going to show you that it's really quite simple to do and I think you'll have a lot of fun with it. The mechanism that we're giving you for adopting Dynamic Type is self-sizing cells. We think this strategy-thank you.
[ Applause ]
This strategy is really going to improve the way that we architect our table views, number one, because you can encapsulate logic right in the cells, and two, the fact that you can do that will make it so much easier to have cells that are not a hardcoded, compile-time, known height which means you can easily adopt the dynamic font changes. So, additionally, we've got some new things in collection view. And number one, really great is we're bringing self-sizing cells to collection view also.
[ Applause ]
And for all of you custom layout authors, I know there's a lot of you out there because, every week at least, I see a new app that has a really great layout that really impresses me. We have something we want to talk about which is smart invalidation, which will allow you to write great layouts that are also super performant.
[ Applause ]
So, we begin by talking about table view and Dynamic Type adoption. If you've never seen this screen from iOS 7, it's when we introduced Dynamic Type. This is a screen in Settings where you can change the text size to fit what feels appropriate for you. And if you're somebody who likes text a little bit bigger so that you can see things easier, then you can slide that over and magically the text changes in the interface and in fact, in iOS 8, all internal apps will adapt and change their text size based on this slider value that you've set.
So that's great because it makes your phone truly feel like your phone, and then adopts to your needs. And since all of the internal apps are doing this adaption, we will expect third party apps to do that as well. So, let's talk briefly about what it takes to get there.
Well, we can do it by using built-in labels. Built-in labels in table view cells have dynamic fonts on them automatically in iOS 8. So, if you're using the text label, the detail text label, they have already dynamic fonts and as the user changes the text size, those labels are going to change size.
If you have your own custom labels, it's really as simple as adding a preferred font to that label. This is another API that existed in iOS 7 so it's not new. We're just expecting everyone to use it now. So, simply changing your font from something that may have been, you know, setting a font of a particular size to instead using a preferred font, you now have Dynamic Type in your label.
But that brings in a wrinkle to things and that wrinkle is now things are dynamic and it's dynamic because of the size of the text that can change based on what the user has set. So, if we think of simple table views like Settings or like Messages-I guess that Notes actually-Messages and Mail or Contacts, these are all simple table views that in iOS 7, the row height was known at build time.
You could say, hey, that's a 44-point high row and that's always true. But on iOS 8, that's not true. A user changes their text size, all of those row heights change. So, even the simplest of table views from iOS 7 now need to think about being dynamic in iOS 8.
So we have a few strategies to achieve dynamism. One is the simplest case. If you're like one of those very simple table views that we saw on the previous page, then you can use the already existing property rowHeight. And even though you're dynamic, you can still use that property because when the text size changes, when you get a new height, you can change the value of that property to something different.
And in fact, this strategy has an advantage if you truly are, you know, a known height that will be the same for each cell because if you can set this property for all same size cells, then you'll get the best scrolling performance possible because no calculations need to be done on the fly while scrolling.
The next strategy is something you're probably all familiar with for using dynamic-sized cells and that's implementing the delegate method, tableView:height ForRowAtIndexPath. That strategy is still valid and something you can use in iOS 8 if you'd like to size your cells one at a time, give a height for each row. You can still do that.
Of course, the disadvantage to this and why we wanted something new is that if you implement that delegate method, all of your row heights are asked for upfront when the table is loaded. Even if you have 20,000 rows, we're going to ask for the height of all of those rows when we load the table. And of course, no cells have yet been created so your delegate needs to understand, without creating cells, what height to make those cells based on your content, and that's been the challenge up to this point. So that's why we have the third strategy, which is self-sizing cells.
[ Applause ]
So, let's talk a little bit about, you know, why this is and why, you know, this is a big deal? If we take a simple table cell like that in Mail, there's a lot of parameters around sizing the cell. We, you know, we look at this and OK, there's actually a lot going on here. There are three different fonts. And in order to size the cell correctly, we need to know about the size of all of those fonts and how big they draw.
Additionally, there are margins between the texts. We need to know how big those margins are and there are several of them. So we have to know these fonts and we have to know these margins. That's a lot of knowledge if we were to do dynamic sizing in the delegate. So, in 8, I'll show you how things work and I'll get out of the way so you can really see this. Imagine that we've got a few rows onscreen, and then we've got a fourth row that's not yet on screen.
In iOS 8, if you're using self-sizing cells, and in fact, part of this is going to be using the iOS 7 API which is giving an estimated height for a row, something we introduced last year. So that fourth row is going to be sized using an estimated height that you provide, either via the property estimatedHeight or there's a delegate method for that, too. Now, when you scroll or when you use the scrolls rather, at that time, we need to bring on screen row four, which up to this point only has an estimated height. So at that time, we will create the cell.
After creating it, we will ask the cell how big it should be. Then, we use that size if it's different from the estimated size to adjust the content size of the table view. And only then after we've adjusted the content size and taken the size from the cell, do we actually put that cell on screen.
And so, what you probably all want to know is what happens in step two? How do we actually size the cell? So, we're going to talk about that now. And there are two ways that you can communicate information about how big your cell is from the cell back to the table view so that we can get you the right thing. And number one is you can use Autolayout.
[ Applause ]
If you're already using Autolayout, and I know that many of you are, you may essentially have to do nothing here. Your constraints, if they already determined unambiguously the size of the cell, then we will just use those constraints. In fact, the way that we've implemented this in iOS 8 is that table view calls systemLayoutSizeFittingSize on your cell. And systemLayoutSizeFittingSize is smart enough to understand if you've implemented constraints in your cell and if so, the Autolayout engine delivers the size. And if you haven't implemented Autolayout constraints, systemLayoutSizeFittingSize calls sizeThatFits. And then, you can simply return a size manually based on your own logic.
So by adding sizeThatFits, you don't have to use Autolayout if you don't want to. If you are using Autolayout as it notes up there, you want to add your constraints to the content view of the cell. You always think about whatever content you have goes in the cells content view. And this plays into what the API contract is when we are sizing your cell because there's sort of a-there's an in and out situation. The in to your cell is a width and that width is the width of the content view.
What your cell returns is the height of the content view. So, when you're implementing sizeThatFits, for example, the size that we send you will have the resolved width, what the width of the content view is going to be. And then, the value that you return has a resolved height.
And even still with your constraints, you should think about how you build your constraints as assuming that you've got a width, the content view has a width and that was predetermined by the table view based on how wide the table view is, you know, whether you have an image view or accessory views that will make us shrink the content view. And then from that width, your constraints define a height based on the content that you have. So I'd like to spend the rest of the time, well, actually the table view time, telling you or showing you in code how to actually do this.
We're going to see that this is quite simple at its base, but I also kind of want to run through gotchas that you might run into, that other people have run into in the past so that you can kind of keep the idea in your head that, "Hey, this is simple," but if I go back to my hotel or if I go back to my company and I start implementing this and it just doesn't seem to work, come back and watch this video because there's a good chance I will have covered what is causing your code to not do what you think it's going to do.
So let's start off by going to Settings and looking at our Text Size. Right now, I've got it set to be the smallest thing. I'll just put it to regular. And this application I've got right now just has a very simple table view and it doesn't do a whole lot.
It just has a one-line text label in cells that are all predetermined to be 44 points tall. So, one thing about this that is interesting, if I'm on iOS 8, is you take a look at that and if I change my text size and I bring it all the way up, then run the app again.
You see nothing changes and that's the problem. We want something to change on iOS 8. We want our app to be adaptive to what the user wants. So, since in this case I'm using a custom label that I've called -headlineLabel here. I created it right in my cell's initializer. The problem is that I have set the headline label's font to be a specific size. And actually what I want is to use a Dynamic Type font. And so I'll just change this to preferredFontForText Style:UIFontTextStyleHeadline. It's perfect.
And if I run that now, we'll see that the text did get bigger and that's great. It adapted to what the user wanted. But this looks a little weird now because the cells are the same size that they were before and I want the cells to grow along with the text because if I have giant texts, small cells, things are weird.
So, what I'm going to do is use constraints via Autolayout and I've got a whole bunch of them here, I'm going to walk through them. We could do this in Interface Builder as well but I thought if we walk through this in code, it will cause a little bit more understanding about what we're actually doing. So, I have four sets of constraint logic here.
What I'm doing with this first one is setting what happens at the top. I'm setting some spacing at the top of my label. So I'm saying that the first baseline is equal to the top of the content view plus a constant and a multiplier. And the reason that I, you know, I've picked these values somewhat arbitrarily, but the reason that there's a multiplier is I want the space between the first baseline and the top to grow not simply equal to the growth of the font but I want it to grow a little bit more so that we get even more space between the top of the text in the top as the font grows.
So then I do something very similar, setting a padding at the bottom. So that's just setting an AttributeBottom for my content view to be based off of the last baseline of the label, again, with sort of a constant that I just picked and a multiplier for the same reason I have a multiplier up here again.
These values are not magic values. They're largely values that I picked out of thin air. So that gives me padding at the top and the bottom of my content. And as the content grows, then I will still have that padding and the cell will actually grow with it.
The last thing I did here for height is, I wanted to have a minimum. The human interface guideline says, "We want to have touchable things, like table views, be at least 44-point tall." So, I've set a constraint that says, "I want my content view's height to be greater than or equal to 44," simple as that. So even if the text size gets very small, I actually still want to keep the cell to be at least 44 points tall to meet the human interface guideline.
For the horizontal axis, I've just used a simple visual format language to say that I want 15-point margins around my headlineLabel and I'm just adding those to the array. I had all those constraints in my content view. And now that I've done that, let's run it and see what happens. Well, it actually looks the same. And, I have something really scary here. I have a bunch of Autolayout constraints failing messages.
[ Applause ]
Thankfully, there is a solution. So, I mentioned this earlier but part of being dynamic-or self-sizing cell-aware and using that in your application is that you use row height estimation rather than literal row heights in your table view. So if we look at my ViewController, in viewDidLoad, previously I was setting the row height to 44. Well, if we do that and we have self-sizing cells, the self-sizing doesn't kick in because I've said row height is 44 and that's sort of the end-all be-all for table view.
What I really want now in iOS 8 is to set the estimated row height. So, when you run into this problem, try to remember Luke said use estimatedRowHeight, not rowHeight on iOS 8. And then when you run this-oh God, Luke's solution didn't even work. Well, so here's one other gotcha here, which actually will be fixed in the next seed, but I wanted to show you-
[ Applause ]
I wanted to show you so you don't think I'm a liar when we go back to your rooms. So, if your cell or if your table view is coming out of a NIB or a storyboard, then its-row height property will be set when it comes out of that storyboard. We want it to be the default. This is actually the new default for rowHeight in iOS 8 is UITableViewAutomaticDimension.
That means, hey, I don't have a row height, figure it out based on other information. And this is already the default for programmatically created table views and, as of seed two, will be the default for table views that come out of storyboards and NIBs as well. It's interesting actually to know this in general that this default has-this default value has changed because it used to be 44 and now it's AutomaticDimension.
Because if you were doing math based on the value of tableViewrowHeight and just expecting the default value to be something that is reasonable to do math on, that's not true unless you have actually set the rowHeight value. So, if you run into weird things, like all of your table cells suddenly shrunk to no width, or no height rather, look and see if you're doing math with the rowHeight property.
So, now that I've done that-oh, thank God, no more weird layout constraint messages. And actually, my cells have grown a bit. They look more natural for this text size. So now that that is true, there's something else that I can do here, and that is go back to my cell and I want to make those labels which are all clipping off the end of the screen not do that anymore. So, let me take -headlineLabel.numberOfLines, make it 0 so it should grow the height of the label according to the content that's in it. If I run that now, now my cells all become the right size based on this content that's in them.
[ Applause ]
And that's it. I don't have any delegate methods at all. I'll prove it to you, nothing. Except for cellForRowAtIndexPath, I mean, got to have that, right? So that's it. That's all you need to know to migrate your table from an iOS 7 style table to an iOS 8 style table.
And I encourage you to go do this right away because you want to be ready by the time we shift iOS 8 to have your fully adaptive apps that respond to Dynamic Type and are just fabulous for users. So that's table view in iOS 8 and we're going to spend the rest of this talk talking about collection view features that I mentioned earlier. And Olivier Gutknecht will come up to enlighten us as it were.
[ Applause ]
Thank you. Hi, my name is Olivier Gutknecht and I love collection views. So today, we have two new features in collection view for you. So first one, of course, is self-sizing cells. We added that to table view and, of course, we had to enhance collection view to support self-sizing cells. But collection view is quite a generic class and a key thing in collection view is you can actually build your own layout.
And when we implemented the self-sizing cells, we added a new infrastructure to do that. And what we wanted to do is to actually open that for your custom layouts. So if you have a custom layout, you can actually implement the same self-sizing cells techniques we have in all different layout.
So, self-sizing cells, we think it's a major new feature in collection view because, with self-sizing cells, you can support easily Dynamic Type. We're making that available in UICollectionViewFlowLayout which is our default layout we provide in UIKit. And it's really for your own custom layouts. But first, I'd like to do a very quick demo of self-sizing cell in UICollectionViewFlowLayout. So, this is a very simple collection view. I actually tried to make that simpler but I couldn't. So, it's a flow layout and we just set the item size of all elements of the screen to be the same CGSize.
So we don't even have a delegate. We just used the itemSize property on UICollectionViewFlowLayout and UICollectionViewCells here are extremely simple. It's basically just a label and everything is sized the same way. So, of course, it's not great. And when you want to actually adjust your item size based on the content of that cell, you can actually do that right now. You have to implement a delegate on the UICollectionViewFlowLayout to compute the correct size for a given item and then the cell is going to be sized with what the layout returns. But it's a bit inconvenient.
Because basically in your data source, in your delegate, you have to compute a size based on the font you're going to use in this label. And then, your cell is implemented with the label. So, you're basically duplicating code and that's not great. So, what I'm going to do now is I'm going to use the exact same collection view, the same flow layout, the same cell class. I'm not going to change to a different layout. I'm just going to enable self-sizing cells on UICollectionViewFlowLayout.
And that's what we have. It's dynamically sized. The flow layout still works the same usual way by dynamically flowing, adding cells line by line and it's a new feature in UICollectionViewFlowLayout. The only thing you have to do is to have your cell class implemented with Autolayout or using sizeThatFits.
[ Applause ]
So, how do you size cells with UICollectionView? The usual way, the classic way to size items on screen with the collection view is to actually have the layout compute items, size and position onscreen. So in that case-and that was what was happening with iOS 6 and iOS 7, the collection view layout decides everything.
The cell is just-We are just enforcing a size for this cell. So, in iOS 8, we're adding these self-sizing cells' mode, which mean that just like with table view, you can actually use constraints on your cell content view. Or, if you are using manual layout, you just have to override sizeThatFits.
But when setting cells onscreen with a collection view, a layout basically computes a set of attributes. And in these attributes, we have position, size, but all the things like alpha or even transform. And we are actually opening that also at the cell level. It means that if you override preferredLayoutAttributes FittingAttributes in your cell class, you can actually tweak for the layout itself to compute. It's extremely powerful. So, to summarize, the first mode, the classic mode, the layout, the size, computes attributes and the collection view is going to create a cell and enforce that size.
The second mode is the layout is going to compute an approximation, provide attributes. The collection view is going to do the usual thing if you create cell and then the cell is going to adjust, if needed, that size. And the third mode, the layout again estimate the attributes but then the cell can actually tweak any property on the layout attributes including transform.
[ Applause ]
So, we are making that available as a new feature of UICollectionViewFlowLayout. How can you use that? It's actually easy. Step one, we have a new property. It's estimatedItemSize. Because on collection view we're really talking about width and height, it's a CGSize. But apart from that, it's basically the same model Luke described for table views.
How do you enable that? You just set this property to a non-zero CGSize. That was step one. Step two is... there is no step two. If you want to use self-sizing cells in UICollectionViewFlowLayout, it's one line. That was my demo. I replaced ItemSize with estimatedItemSize and that's it. That's pretty cool.
[ Applause ]
Now, I'd like to talk about how that works and how that changed things in the collection view model. Self-sizing is really a collaboration between three things, the layout, the collection view and the cell. The first thing a flow layout is going to do is to compute a first pass, the first approximation of your layout.
And based on this information, what's going to happen is the collection view is going to the queue, create these cells. We're going to self-size these items again using Autolayout or sizeThatFits or preferred layout attributes for fitting attributes. And then, we are actually sending back that updated information to the layout.
The layout can now decide if it wants to react to that and actually send the final layout attributes we need to actually display cells onscreen. So, a very important thing about this model is it's actually the same model we're using with collection view. So, layout is in charge and is always in charge. But the catch is now we have a way to actually communicate to the layout that maybe we should actually adjust things because the cell size might be slightly different from this first approximation we computed before. That's a very generic and extremely powerful mechanism.
So, now, I'd like to talk about custom layouts and smart invalidation. But first I'm going to refresh your memory about how collection view layout and validation works. So, if you have a custom layout, if you got a custom layout, you know that the first time we try to present cells or supplementary views or decoration views onscreen, the first method we are coding on your custom layout is prepareLayout. Usually, that's when you can actually pre-compute things or cache some attributes, have a generic idea about what your layout is going to be.
Because the next thing we're going to ask you is the overall size of your custom layout. And based on that, and based on what section of this layout we want to show onscreen, then to ask the layout to provide a list of attributes for items in a given rect.
For instance, usually the visible bounds of the collection view. Based on these attributes, the next thing is the collection view is going to do the usual work of dequeuing, creating cells, possibly coding sizeThatFits or applying Autolayout constraints. So, in that case, you would return 6 and 6 plus the supplementary view attributes describing what is in this rect.
Now, you might actually later invalidate this layout, for instance, because you want to apply some special effect when you scroll, like a cover flow-like effect. In that case, you have to call invalidateLayout, which is basically a way to tell the collection view that OK, we are starting again. What's going to happen is we begin to call prepareLayout again. And that full cycle starts again.
So, obviously, there is a small problem here. If each time we call or you call invalidateLayout, you have to re-compute all these attributes. Not the best way to have a high-performance layout. So, we actually have a great solution for you for this problem. Be lazy or smart or lazy and smart which basically means re-compute only what you need.
And that's a strategy you can use right now in iOS 7 with something we call invalidation contexts, because that's a class that we introduced in iOS 7. And this class is basically a tool for your custom layout to indicate what changed, because it's your layout so you know what to do. So, invalidation contexts in iOS 7 were basically a way to provide fine-grain information to your layout when things are invalidated.
We were actually using this invalidation context class already for things like rotation in UICollectionViewFlowLayout. And now, we are using that for self-sizing cells. So, in iOS 7, when using an invalidation context, it's actually easy. You have to define a collection view invalidation context subclass exposed at your layout level. And instead of coding invalidateLayout, which is the very generic, remove everything invalidation method, we can actually call invalidateLayoutWithContext and pass an instance of your class where you're probably adding some information about what you should do in a given invalidation situation.
We also provided an override point for bounds change, which is a common thing you want to do. So if your layout is continuously invalidating on scrolling on bounds change or rotation, you can actually just override that and return your invalidation context with information you can use then in your layout implementation.
We actually added in iOS 7 in this invalidation context two additional information. Sometimes when we invalidate, it's because the data source changed. You have items coming in on or out or we just have to invalidate everything in some situations. These were two properties. The collection view was setting up for you and you could use in your layout implementation.
So, iOS 7 invalidation contexts were really a class for your own use basically. So what's changing in iOS 8? In iOS 8, invalidation contexts are really a way to establish a communication between your layout and the collection view. You can tell the collection view that, "Here is my invalidation situation.
Maybe you can actually help me with that." And the information you can pass to the collection view can be useful for things like high-performance floating headers, sticky headers, like in table view when you scroll and you have this section header on top. Another use case is self-sizing cells.
Oops. Because when cells can actually change their size, it might actually change the overall collection view content size. It might change the offset and invalidation context are a great way to tell the collection view that your layout is adapting to a self-sizing change. So, first, fine-grain invalidation. We added three new methods on invalidation context in iOS 8, invalidateItemsAtIndexPaths, invalidateSupplementary ElementsOfKind atIndexPaths and invalidateDecoration ElementsOfKind atIndexPaths and you can access this later. What's great about that? First, you can actually call these methods several times. You're going to aggregate that information. What does that mean for your layout? Let's take a very simple, grid-like layout with section headers. And you want to implement a sticky header on top.
In iOS 7, you basically had to invalidate the entire rect. In iOS 8, the only thing that is actually moving is that supplementary view. So what you really want to use is, on your invalidation context, tell us that I'm invalidating that supplementaryView atIndexPath throughout. In that case, we know that only one thing is actually changing.
So, we're going to ask your layout, "OK, give me an updated version of that supplementary view." So we are directly going to call this fine-grain method on your layout which is already here, layoutAttributesFor SupplementaryViewOfKind, section header atIndexPath. And one thing we are not going to do is calling layoutAttributes ForElementsInRect. So instead of asking you for 14 attributes in that invalidation situation, we are just going to ask you for one. It's amazing for high-performance layouts.
[ Applause ]
Here's a situation where you want to use an invalidation context is for self-sizing cells. If you want to implement self-sizing cell in your own custom layout, well, you can actually tell the collection view that the content-size is going to change because that cell was bigger than expected so you have to enlarge the overall content-size. And you can do that with a very simple property on the validation context which is a contentSizeAdjustment. So you can give us the delta, the change between what was computed before and the new size.
But, of course, because a cell might not be what you expected, it means that the offset where you are in the collection view can change and you can tell that too. You can set to contentOffsetAdjustment in the invalidation context and then the collection view is going to do the right thing and adjust the position so you don't see and they jump from one position to another just because your layout updated itself.
The other feature we're adding to help you with self-sizing cells in your custom layout is a way to invalidate for specific attributes. And that's a new method on UICollectionViewLayout. If a cell returns a different size, for instance, or different layout attributes, we're going to ask you if we should invalidate the layout.
And because we want fine-grain invalidation, we have an override point, invalidationContext ForPreferredAttributes based on the original attributes. So again, first step, your layout computes the first approximation. We self-size the cells which might change its attribute and we pass you these attributes in your layout. And again, the layout is still the final decisionmaker on what should be on screen.
So, we just like to summarize that technique for a custom layout. It's a very simple, line-based custom layout, something like that. So that's my overall collection view layout. I have five cells and I'm going to compute that first approximation by using the usual layoutAttributes ForElementsInRect. So I'm going to assume that all my cells are exactly the same size.
But then, collection view is going to create these cells and self-size. In the first cell, let's say, adjust to this size based on sizeThatFits or through layout or preferredLayoutAttributes. And obviously because of that size change, my layout is no longer what I want. So I will have to update the position for these other cells. So, in my layout implementation, I can decide if a cell has different attributes from what I computed before that I should actually update these other cells' attributes.
Of course, there is one last problem. My collection view content size is no longer correct. So, I want to make that consistent. So, in my invalidation context, I'm going to tell the collection view that we should adjust the collection view content size. And actually it's not the only thing I need to do because my previous state was three cells in these visible bounds. A little bit of the first cell, the second cell and part of the third cell. But the next thing is telling the collection view that based on this new cell information we need to actually adjust the offset, so visually we are in the exact same state.
[ Applause ]
It's, of course, completely layout-specific. You can imagine using this technique for circle layouts, line layouts, grid layouts, baseboard layouts. It's an amazing way to implement extremely high-performance layout and add new features like Dynamic Type. And to conclude, I'd like to bring back Luke.
[ Applause ]
Thanks so much, Olivier. Well, you probably got a theme here that is we've brought self-sizing cells to table view, we've brought self-sizing cells to collection view in the form of flow layout and we've also brought self-sizing cells to your custom layouts if you choose to adopt that methodology. So, what I want you to go home with is, remember that every app should be adopting Dynamic Type, if you've got text in your app that is, which I imagine is every single one of you.
When you're doing this, you might think about using self-sizing cells. It will probably help your cause. And if you're using collection view, specifically, if you are the author of a custom collection view layout, this I can't stress enough, use invalidation contexts. This is like the key to the world for writing high performance layouts in collection view.
In fact, since we're going to have a lab right after this, and I know some people are going to come with performance questions, I'm going to tell you that my first question for you will be, "Are you using invalidation context?" And so, if you're not, learn how to use them. They're spectacular.
There's another great talk on collection view later today particularly for those of you who are writing custom layouts. This is "Advanced User Interfaces with Collection Views" and there are some really great tips in here for how to accomplish non-standard things, you know, things that aren't built into the flow layout that we shipped and get truly great interfaces that use collection view. For more information, always feel free to contact our Frameworks evangelist, Jake Behrens, and check out the documentation. Thanks for coming.
[ Applause ]