Collection View Layouts make it easy to build rich interactive collections. Learn how to make dynamic and responsive layouts that range in complexity from basic lists to an advanced, multi-dimensional browsing experience.
My name is Steve Breen. I'm engineer on the UIKit team.
And I'm joined today by me colleagues, Troy Stephens from the AppKit team; and Dersu Abolfathi from the App Store team.
So today we're going to talk a little bit about advances in CollectionView layouts. Now, we're going to break the talk down into four broad segments. First we're going to go over the current state-of-the-art -- how do we do this today? How do we define CollectionView layout in our applications? Then we're going to go over a brand new approach that we're bringing to all the platforms this year -- iOS, tvOS, and the Mac.
And then we're going to go on to some hands-on demos so you can see this API in action.
And then we'll do a tour of some more advanced features of how you can get the most out of this API.
All right, we got a ton of content. So let's get started.
All right. So first let's talk a bit about the current state of UI. What do we do today? How do we define CollectionView layout? Now, when CollectionView was introduced back in iOS 6, it has a really novel concept; it had a separate abstraction for defining layout.
So it's really two classes acting in concert with each other, where one does a rendering and one is responsible for where things go or the CollectionView layout.
Now, CollectionView layout is an abstract thing. So we have to subclass to get use out of it. And we shipped a concrete layout class with iOS 6 called CollectionView flow layout.
Now, flow layout was really useful for a ton of different designs, especially back in the iOS 6 days where things were maybe a little bit simpler.
And it did this because it uses a line-based layout system. Now, we covered this last year in a tour of UI CollectionView.
But in summary, a line-based system allows you to lay out on the orthogonal axis, of the layout axis, until you just fill up the amount of space you have available and we drop the next line.
And this is really great, it works simple, easy to reason about. You can get it up and running pretty quick.
But what about today's apps? So as devices got more heterogeneous and screen sizes got different, things get mother complex in today's apps. And here we see the App Store that we're shipping in iOS 13.
Now, if your designer handed you this design today, what would you do? Well, you'd think, "All right, I got to choose how I'm going to design this thing." And there's a lot of options now more than never.
And you might settle on CollectionView. And if you did that, you would think to yourself, "Well, can I really make flow happen here?" And you'd probably discard that right away.
So at that point now you're facing a custom layout. And, you know, I see what you guys say on Twitter about custom layouts. And they can be complicated. So last year we talked about this in a tour of CollectionView.
And we talked about what's involved with building a custom layout.
Now, there's a certain amount of stuff you have to provide in your concrete layout class, and we walked through those things.
And we also walked through the performance considerations you had to think about when you're designing a custom layout to make sure it's fast if you have a large number of items in your CollectionView.
But we didn't cover everything, and there's additional challenges if you're building these custom layouts.
And there's a number of those, but I'll cover a couple here, you know, supplementaries and decoration views -- two of the view types that you can manage in CollectionView -- are a little tricky in custom layout. You're on your own.
And there's also self-sizing challenges you have to wrestle with.
And we'll get into that more later.
So this year we're bringing a brand-new concrete layout class to the platforms that sits right alongside flow as a peer.
And we call this compositional layout.
Okay. So -- Haven't even seen it yet.
So -- so what is compositional layout? What does this thing do? Well, just a little philosophy right up front. Hey, what is this built on? It has three basic tent poles that we built compositional layout on. First, it's composable -- the idea of making complex things from simple things.
And it's designed to be flexible.
You can write any layout with compositional layout. It's extremely flexible. And it's also fast by default. So we've taken all of the performance optimizations on ourselves in the framework so you don't have to think about it. Compositional layout's all about describing or defining what you want to do. It's a declarative kind of API.
All right, so composing -- you're going to hear this word a lot in this conference. So how do we do that with this compositional layout idea? Well, it's all about taking small bits of layout, these little components, and stitching them all together.
So you're composing larger layouts from smaller bits of layout.
And we've learned from the great lessons of flow layout where we bring some of those line-based lessons where you can lay out along a line. We may now how many items there are, we may not. But we can lay out items on a line in these little layout groups.
And finally, as the name implies, you don't subclass. You literally just create a thing and compose some elements and then -- and then you're good to go. All right. So that's a lot of talk for no code. And we're all about code at a conference like this, right? So let's go look at some code.
So this is a hello, world compositional layout. There's five lines of code. Now, I'm going to switch over to my device over here.
All right. So here we see our example app. And I'm going to go to the list example.
Wow, that's a boring layout. Okay, so this looks like a table, right? Here we go.
Not much to that.
All right. So let's focus strictly on the code for a second. Let's look at this.
All right. So there's a couple observations that will jump out right away. And the first observation because I know developers is, like, "Hey, Steve, I can write this in two lines of code with flow. What is all this nonsense?" And it's absolutely true, you probably could do something like that. But what I want you to anchor in your mind and think about throughout the presentation is that the amount of code as these layouts get more and more complex does not grow linear to the problem size, it just kind of tapers off. Because we're just going to compose in new things to the layout in very simple, easy-to-reason about ways to get very complex layouts.
I'm super excited to show you this stuff.
The second observation is I want you to look at the -- there's a natural progression here of these types, right? We got, like, you know, five types here. It's kind of -- what's up here? And the types I want to focus on first are these four types, and they have this natural abstraction where they climb this ladder. And we start with an item which gets folded into a group. And the group goes into a section.
And the section goes in our layout.
Now, let's look at this visually.
All right. So here, this big rectangle is the layout. And the layout is your entire layout.
And now we have these blinding white guys that represent our sections, right? And this maps directly onto the data sources, you know, content for those sections.
And then we're going to represent a kind of a grid-style traditional layout in this particular example. We see these groups which represent our rows.
All right. So inside that we have items.
So this is basically just showing off this hierarchy we're going to see all throughout the talk, this repeating pattern of item, group, section, layout.
Okay. So now I want to do a little bit of talking about some of the concepts, these core types in compositional layout. And once we get through all this, we can jump over to the demos to see how it all fits. So I want to start with talking about sizing.
So compositional layout has extended sizing to make it really easy to reason about how you size things inside of a compositional layout.
And everything has an explicit size, has a strong opinion about how big it is.
Now, we're in this Euclidian 2D geometry plane, all right, with flexion view. And as such, a size really is just two properties. It's a width and a height dimension.
And here we can see we've got a stripped-down version of that type definition. And it has a width and a height dimension. But notice that the width and the height dimensions, they're not -- they're not scalar values. It's not just a float or something, it's actually another type, this NSCollectionLayoutDimension.
All right. So what's that? It's really simple. This is an axis-independent way to describe how big a particular axis is. And we have four different variations of how to define this thing.
And let's walk through these in a kind of a visual way.
So let's say you have an item and you want to describe its size relative to its container.
So the outer-most container will be your CollectionView.
Here we would just say, hey, this item's widthDimension is going to be a fractional width or 50% of the width of its container.
And similarly, we can say the height of something is a fractional height of its container, in this case 30%.
Now, because you can specify things in this axis-independent way, we can define something as having a specific aspect ratio, in this case an aspect ratio of 1, by defining both the width and the height as a dimension, a fraction of the width of its container.
And we say it's 25% as its container, and the height mirrors this.
Okay. So those are the fractional variants creating a dimension.
What about point-based values? Well, we have two.
First one is the simplest, absolute.
You know you need this thing to be 200 points because your designer's emphatic it's got to be 200 points. That's what it is, right? Here we go, we've got an interesting concept, estimated.
So if you don't know exactly how big an item is going to be -- and we're going to talk about this a lot -- you can estimate it. Say it's 200 points.
And then over time, it grows as the item renders and we know a little bit more about the content in that item.
All right. So that's layout dimension and size.
Next up let's talk a little bit about item. This is very simple. This is a cell or it's a supplementary. It's a thing that renders on screen.
And there's more stuff here you'll see in the STK, but it's a taste of that type definition.
We see that when we construct one of these things, we give it a size.
Everything's got an opinion about size.
All right. So we're going to continue up that abstraction hierarchy. We got item, and now we're in group.
And what's a group? A group is -- it's the workhorse. This is your basic unit of layout that you're going to compose together. We have three forms: We have horizontal, vertical.
You can think of these as, like, little mini flow layouts, right? They lay out on a line on the horizontal axis and vertical axis.
But then remember we talked about before that it's flexible. Well, if you have something that doesn't lay along a the line and there's a lot of layouts that don't do that.
We have a custom group.
So what is this? That allows you to specify the absolute size and position of items in a custom way. So if you have something where you have a pre-defined generator that generates layouts, you can use a custom group.
Or if you're doing a radial layout and you want to compute that, you can do it with a custom group.
And what's cool is you can compose custom groups right alongside vertical and horizontal. So we build up the complex from the simple.
All right. So that's group, the workhorse.
Next up we have NSCollectionLayoutSection.
This is just as the name implies, this is the layout definition on a per-section basis of the CollectionView, maps directly onto the data source notion of how many items are in that section. And we can see, as promised, that when the initializer here takes in allow group.
So we've gone from item, group, section.
All right. Our final stop on this traversal of stuff, these are the two top-level layout classes, right? So for iOS and tvOS, we had UI CollectionView CompositionalLayout.
And on the Mac we have NSViewCompositionalLayout.
Now, what's interesting here is the definitions for all these things are the same, regardless of platform.
There's just some minor differences in the top-level classes.
All right. And the final thing to note here, and this is actually really interesting, which we'll see a little bit more in the demos, is the way you construct a compositional layout. There's two ways.
And the simplest way is just specify a layout section's definition. So this is very similar to what we do with flow today, right? Because flow weighs out every section like every other section. It's homogeneous in that way.
The compositional layout extends this idea.
Because now we have this great definition for what a section is, you can specify a closure that will be called back and will ask for those definitions for the sections on a per-section basis.
Now, this opens up a lot of possibilities because now your layouts can be completely distinct between sections. And we have a lot to show you in the demos of this later on. And hey, later on is right now. So enough nattling on about types, we're going to go see this in action. And to do that, I'm going to bring up my colleague, Troy Stephens. Troy? Troy Stephens: Thank you, Steve.
As promised, we're going to look at some code. We're going to dig right into the practical mechanics of how you can build just about any kind of layout you can dream up from these simple elements that Steve just described.
So make sure to download the sample project for this talk if you haven't already.
That way you can follow along, study it at your leisure, and most importantly, use our code freely in your own projects.
Whatever kind of layout you might be thinking about implementing, there's likely to be an example we're going to look at today that is similar.
By taking our code as a basis for yours, you're going to be that much closer to your goal that much more quickly. We're going to see that it's really easy to take any existing compositional layout description and incrementally refine it to be exactly what you want.
Now, as we walk through our examples today, I want to you notice the same basic pattern at work in each of them.
This is the pattern that Steve introduced us to.
Every single compositional layout description is composed of the same four basic parts: Item, group, and section descriptions wrapped in the overall containing layout.
So in each of the code examples we're going to look at, we're going to see that same four-layer nested structure.
And in the more advanced topics, we're going to see how you can actually nest groups inside of other groups in order to construct or compose more complex, sophisticated layouts out of simple, easy to understand parts.
So let's get to the code.
And here we have -- we're going to start with our list example, which is the one that Steve showed us. So here's -- this is about the simplest kind of useful layout we can imagine. It's a single-column list where the items span the width of our CollectionView.
And if I rotate the phone, we can see that indeed the items expand to fill the available width while maintaining the same constant height.
So how do we implement this using compositional layout? I've got our list view controller source file open here, which is the one where there is implemented. And in each of our examples today, we've just gone ahead and declared a create layout function that neatly encapsulates our description of our compositional layout that we then return back out to be hooked up to our CollectionView just like any other CollectionView layout would be.
So first thing to notice here, we start, as I promised, by describing an item and itself size.
We take that item description and we use it to describe a group of items.
Next, we wrap that group in a section.
And finally, we create and return our compositional layout.
So there's that item, group, section layout structure.
The other thing I want you to pay attention to here and we should really understand before we move on to the other examples is the way that the items are given their size.
In this case it is the group size that ultimately ends up determining the item size. And I'm going to explain how that works.
So groups might seem a little superfluous in this simple list example.
A group in a compositional layout typically represents some repeating structure that you're going to have, a column of items or a row of items.
In this case it's a row, but we really have kind of a trivial case where we only have one item per row.
So each item is going to get its own group.
But groups are always there as a consistent part of the compositional layout description, and we're going to use them to our benefit to help define the item sizes.
So Steve explained all about container-relevant sizing.
Let's look first at our group size description.
We've asked here for our group, which is our row in this case, to span 100% the width of its container.
The group's container is its section, which in turn spans the layout or the CollectionView.
Meanwhile, we've asked for each group's height to be an absolute value of 44 points tall.
Now, notice that this has basically already defined what our item box should be -- that's how we want our item sized, the width of the CollectionView and 44 points high.
So all we need to do up here where we specify our item size is to say that we'd like each item to be 100% the width and 100% the height of its container, the item's container being the group.
So that's all it takes to implement a list. But the interesting thing as we go along, we're going to see that we don't have to make a lot of code changes to get some pretty dramatically different layouts.
Let's take a look at another example, that being I'm going to open up our grid here.
And this is a five-column grid of edge to edge items.
And we can see if I rotate the phone, that this layout is indeed described in such a way that we always get five columns across. The items always remain square, and they're always sized such that we get exactly five columns wide.
Now, this is an example where groups actually come in a bit more handy and it will be easier to understand what their function is.
So opening up the grid view controller source file, looking at our same create layout function, again, we have that item, group, section layout structure.
Basically looks like our list layout description. The only thing that's different here is the item and group sizes that we specified. So let's look at how those differ.
We still want each group -- since a group represents a row -- to span the entire width of the CollectionView. That's great.
But now we don't want our items to be the same width of their group; we want to have five items across. And the way that we achieve that in this example is up here we specify the item's width to be 20% of the width of itself container.
The item's container is its group or row, so we're going to have -- we're going to be able to fit exactly five items across because of this width that we've specified.
Now, as for the heights, instead of an absolute point value, we've declared that we want the height of each group or row to be 20% of the width of the group's containers. And notice we're using this ability to specify fractional widths or heights across axes here. And this is really handy because it's how we make our items square.
The item width and the group height are the same.
And since the group's height sets the item's height, all we need to do is set our item height to be 100% of the group's height.
So that's it, that's pretty simple for creating a grid. And we did so without really fundamentally different code than we used for implementing a list.
Now, often if you get a layout from your designers, usually you'll want to have some space between items. So let's look at how to add that next.
Bringing up the inset items grid example, we still have a five-column grid, but we just have some space now around and between the items.
Let's open up our inset items view, InsetItemsGridViewController to take a look at that.
And here we have, if you compare this with the previous example, you'll see that there's only one line of code different here. And this is a useful observation because you can really think about this layout as being computed almost exactly like the previous edge-to-edge layout.
Each item is allocated the same box that it was given before, the same edge-to-edge squares.
But in this case we've decided that instead of sizing our items to take up the full square they're allocated, as a last step we're going to inset the item's content by five points on each side. So item content insets work usefully in this way. Sort of a last-stage subtraction from the layout that was already computed.
So that's really cool. We've seen how to do lists and grids.
And we've achieved our grids using relative item sizing.
But there's another really powerful way to be able to grid-like layouts with rows and columns.
And I want to show you that so we can be familiar with it.
So I'm going to pull up our two-column grid example here.
And superficially this looks pretty much just like the five-column grid, we just have fewer columns, right? And indeed, if I rotate the device, it adapts: It's always two columns and the items expand to be the appropriate width.
But this is implemented a bit differently and it's instructive to take a look at how.
So in our TwoColumnViewController here, we're going to look at our createLayout function.
And so the interesting things to notice here, again, we have that item, group, section layout structure. But the first thing to pay attention to that might not be immediately obvious is that we are constructing our horizontal group that represents each row in a slightly different way.
We're using a different form of the initializer that takes an explicit count parameter.
Here we're explicitly specifying that we want to have exactly two items per group, two items per row.
Now, this will cause compositional layout to automatically figure out what the item width has to be in order to make that happen.
We do specify an item width here because we always have to. We say 100% of the container.
But that value at the top ends up effectively getting overridden. When you ask for a certain number of items per group, compositional layout -- that compositional layout is going to take that as kind of an override and it's going to compute whatever width is actually necessary to fulfill our request. We're also using a different way of putting space around and between the items. Compositional layout offers a variety of ways to do this, which makes it a really flexible API.
Instead of specifying itemInsets in this case, we're specifying contentInsets on the section.
So here we want to have a little margin on the left and right side. And we only have one section, so this basically applies to our whole layout.
So we're asking for 10 points of leading and 10 points of trailing space.
Notice while we're on this line of code that compositional layout is structured to encourage you to express your layouts in layout direction-agnostic ways. So instead of left and right explicitly, we're specifying leading and trailing. And this is fantastic because when you run your app in a right to left language, you're going to automatically get the right layout adaptation.
Here we're also using a property called interItemSpacing on the group. So we can ask for a group to put a certain amount of space between its items, in this case fixed spacing of 10 points.
And that's it. Everything else is very similar to the previous examples. We're just using that ability to explicitly specify the number of items per group.
So that's all pretty neat.
But Steve mentioned another really cool capability that I want to dive into.
He mentioned that it's possible to have a different layout for each section. So far we've only looked at one section layout so far, but what if we want to have multiple sections, yet have them able to each have their own distinct layout? So let's open up our distinct sections example.
And here we have a layout that's sort of a composite of some others that we've done.
So we have three sections in this layout.
The first section is a simple, single-column list like we had before.
The second is a five-column grid of square items. And the third is a three-column grid of rectangular items.
So how do we implement this? Let's open up our DistinctSectionsViewController and look at our createLayout function.
Now, at first glance this looks somewhat significantly different, but it's really just that the outermost component of it where we instantiate the layout, instead of coming at the end, it's coming at the beginning.
And here's the reason why.
So we're instantiating the compositional layout using an initializer that takes that section provider closure as its parameter that Steve mentioned.
So section provider closure, I mean, this is an arbitrary block of code. So as you can imagine, you can respond to this being invoked by returning whatever kind of layout you want for that section.
Your past two parameters, section index that tells you which section -- it's going to be zero, 1 or 2 in our case.
And a layout environment that contains various useful properties you can refer to. We're going to look at that in a moment.
So -- but everything else inside this closure is just the same code that we wrote before. We're specifying an item description.
We're wrapping that in a group description.
And then lastly, we specify a section description, and that's what we returned from the section provider closure.
Now, compositional layout knows to automatically invoke this closure whenever it needs to requery for a new description of a particular section.
So the contents are pretty much very similar to before.
The interesting thing that differentiates our various layouts is mainly that we have a different number of columns, right? So we've declared this SectionLayoutKind type, it's declared at the top of the source file. Let's take a look at. When we initialize it, we pass in the section index that's going to be zero, 1, or 2.
And all it does is map that to an enum type that tells us the SectionLayoutKind is going to be either a list, or grid5, or grid3. And we've also added this handy gettable property, columnCount, where we can just ask that SectionLayoutKind value how many columns we should have in that layout.
Going back to our createLayout function we can see where we ask for that columnCount, and we use it in two different places.
As in our two-column grid example, we're explicitly passing the number of columns that we want when we instantiate our horizontal group.
So we're leaving it to compositional layout to automatically figure out what sort of item width that implies.
We're also using the number of columns here to determine the group height or the row height for the layout since we want to vary it. So you can do this any way that you want.
So that's pretty neat. But what if we want to be able to have this layout adapt, right? What if we rotate the phone and maybe this isn't making the best use of space? Maybe we could fit more items.
So let's look at our adaptive sections layout.
Now, at first glance this looks exactly like the previous example.
But when we rotate this one, we can see that our first section adapts to show two columns across; our second shows ten across; and our third shows six across.
So how do we implement this in code? It's actually quite similar to our previous example.
Opening up our AdaptiveSectionsViewController and looking at the createLayout function, this is very similar to the previous example. The first thing that we'll notice is different that we've changed our SectionLayoutKind type.
So we still have the SectionLayoutKind.
But columnCount is no longer just a gettable property, it's a function that takes a parameter. And we're passing it a width that we're getting from that layoutEnvironment. So that layoutEnvironment type contains information such as the overall container width that your layout has to work with, and on iOS it also has trait collections info inside it. So you can use all of that information to figure out what kind of layout is appropriate for the current environment.
Here we're just using the width. And if we look at the top where we declared SectionLayoutKind, we can see that columnCount function that's now a function, and it takes a width parameter that we passed in. That's the width of our CollectionView basically in this case.
And we've basically implemented a layout break.
We decided that if we have more than 800 points of width to work with on our device, we're going to go into what we call wideMode.
And in wideMode we simply return a larger number of columns for each section.
And that's it. If we go back to our create layout function, we can see that after we've got that number of columns, we pretty much use it in very much the same way as before. So it's very little code change to get all the way to having an adaptive rotating layout.
So this is all very neat. But so far we've only really talked about items. We haven't even delved into what you can do with supplementary views and decoration views. And one of the really cool things I love about compositional layout is that it makes it easier than ever to be able to go beyond just headers and footers and work with arbitrary supplementary views of your own design.
So to tell us a bit more about that and to take us into some of the more advanced topics about other cool stuff you can do with compositional layout, I'd like to invite my colleague Steve back on stage. Thank you.
Steve Breen: So now that we've seen how the basics work, how you get up and running with compositional layout, I'm going to walk through how we deal with more advanced topics. Because there's a ton of different ways you can do custom layouts with compositional layout.
All right. So first I want to talk a bit about supplementary items.
So CollectionView manages three basic view class types: Your cells, the things you interact with to express your model objects; it also represents supplementary items and decoration items.
Now, these are meant to adorn other parts of your layout to give you visual cues about content information, like maybe a badge on a cell that says, "Hey, you got a comment on your tweet," or whatever.
And we see common uses for these today with these three examples -- badges, and headers, and footers.
And we have support and flow today with sticky headers and footers. And they float above that content.
But we extend this in compositional layout, making this a whole lot easier.
And we can simplify this with this notion of being able to anchor content onto an item or group in your layout. It simplifies that visual relationship, how that might work. All right, let's look at this visually. So here we can see we've got this new type, NSCollectionLayoutAnchor.
And here we specify the relationship between these two types. So our supplementary is going to be anchored relative to the geometry of a host space, an item or group in these kinds of ways.
And it's super easy to reason about.
Okay. So here we see we have NSCollectionLayoutAnchor we create right away. And we specify the edges. We want this item to be pinned to the top trailing side of that particular cell.
And we want it to poke outside of that geometry a little bit. And we see that with fractional height.
Okay. So here we can see the device. So we've got these badges on there, right? With my wonderful design skills. We've got a four-item grid with those wonderful corn flower blue cells.
And we see here that some of these items have these little notifiers on them, right, letting them know, "Hey, you got something to pay attention to here." And with this, they kind of poke outside that geometry a little bit, right? They're not really inside the geometry of the cell itself.
Let's go back to the slides real quick and walk through this.
All right. So that fractionalOffset is what buys us the ability to poke outside a little bit. We're going to move over fractionally 30% in the positive X and then up in the minus Y 30% as well.
And then we see we defined the CollectionLayout SupplementaryItem with a badgeSize and elementKind. So we're going to refer back to the view class for CollectionView with that registered supplementary type.
And then we specify the container's anchor, specifying how it's going to relate. So now that we have this definition of our supplementary, we need to associate it with something.
And that needs to be associated with an item, a cell.
So in this case we're going to initialize it with an extended variant of initializer that takes an array of supplementaries. And that's it.
All right. So what about headers and footers? So headers and footers are just a tad bit different than supplementaries for these items. When you think about the content that you want to adorn with a supplementary header and footer, you really don't want that supplementary heard and footer to occlude the content; you want to extend that content area so you can see the content itself.
So in this instance we have a different variation of supplementary called boundary supplementaries. We're going to bin it to the boundaries of that supplementary -- of that host geometry.
Now, we can do these boundary supplementary items for sections or the entire layout, and we can pin them to this whole balance, which do some pretty cool stuff.
All right. So I'm going to switch back over to the device here hopefully.
There we go.
All right. Let's see what this looks like.
So pretty straightforward example. Here we have a pinned section headers and footers.
And we scroll and everything looks just like we expect.
Look at the code.
All right. So here's what we got.
So it's similar to what we say just a minute ago with the BoundarySupplementaryItems, except now rather than that container anchor, we see we have alignment property.
And here we specify top for the header and bottom for the footer. We want to go to the top and the bottom of that section of geometry.
And to make sure that header kind of floats and stays pinned to the content area where it's at in the section, we just specify PinToVisibleBounds.
And then we need to associate the header and footer with the geometry it's going to be in, and that's the section. We just do that with the BoundarySupplementaryItems array.
All right. So by now you've played around with the brand new iOS 13 card presentation, this whole card design language all throughout the system.
And we see this in scrolling UI's as well where all kinds of content is grouped together logically with cards.
And this is a natural fit for CollectionView because we always had support for notions of decoration views. Well, in the past you had to do the math yourself. Well, now we've made it a lot simpler with compositional layout.
And we support this with a CollectionLayoutDecorationItem.
You just create it with an element kind and you're done.
This is intended to be used to have a view that's behind the section content itself to give you that nice visual grouping.
And to construct it, there's just one line of code. And then to add it to the section, you just want to specify the items and off you go.
Here we go.
All right. So let's take a quick peek and see what this looks like.
All right. Pretty straightforward.
So there's our list with multiple sections, and we have our decoration views with just a single line of code. So pretty simple.
All right. So here's a topic near and dear to my heart: Estimated cell-sizing.
So in iOS 13 we spent a lot of time making this fast and accurate.
And compositional layout extends the notion of estimated cell-sizing in very specific ways. It allows you to do per-axis cell-sizing. And this is super important.
Because oftentimes the view hierarchy that you want to get the content of when you render the content, you don't want to necessarily be fully constrained and wild on all the axes and grow on the X and grow on the Y.
You might know how wide the thing is, for example.
An example of our headers and footers, we might know that we want it to be exactly the width of the CollectionView. But, you know, we want the height to vary a little bit.
So I'm going to show an example of this real quick. And we've already been in this code before.
So here we see our very familiar sections, headers and footers. I'm going to pull down here and go to my text size widget and ramp it up.
All right. So here everything's just adapted. It's done the right thing, right? Now, how do we do this? All right. So here we can see that the height dimension's estimated.
That's all we did differently here, right? We specified that we knew exactly how wide this thing's going to be, but we don't know about the height. We just estimate it to be 44 points.
So as the content's rendered, we have a better idea. We can invalidate the layout automatically. And this happens all automatically, makes it really easy to support dynamic type in your applications, even with supplementaries and headers and footers. So this is -- this is pretty great.
All right. So now we're getting to some really fun stuff. So what about nesting things? So we talked about this notion of composition early on. Well, let's talk about how that works.
So the core layout thing inside of compositional layout is layout group.
And layout group is actually a subtype of NSCollectionLayoutItem.
Now, because this is a relationship, when you specify the items that are in a layout group, you can also have other groups. So you can nest these. And there's no limit to this particular nesting, it's arbitrary. And because we have this, it unlocks a ton of interesting new designs.
Okay. So in this example we see a group is compromised of three items.
And we see this big old monster on the leading side and it has a vertical group on the trailing side, right? So how would we reason about this in code? All right, so this is pretty straightforward, right? We had this horizontal group on the bottom here. And its subitems are the leadingItem itself, right, and a trailingGroup. So it's very easy to reason about these kinds of things. We can see right away what we're trying to do. And we just compose in additional stuff and we get these great layouts.
All right. So nested groups are pretty cool. But what about -- what about nesting CollectionViews? All right. So here we see the App Store, the refresh for iOS 13.
And I don't know about you, but when I was a third-party developer, if I saw this particular design handed to me, I'd have a heart attack. This is complicated stuff. This can be challenging. There's a lot of bookkeeping involved. And -- but it's a common pattern. We see it all the time in today's applications.
And to make this perform well and look great is a bit of a challenge. Well, compositional layout solves this with one line of code.
And I want to show you a demo of this right now.
Okay. So here we have the same group I had from the prior one. But we notice the group's a little bit squishier, right? It's about 80% of the container's width. And this is a vertically scrolling CollectionView. It's got five sections on it.
But each section scrolls orthogonally with that single line of code.
All right. And we've got a ton of different variations of this. And bear with me. I'm going to switch around here. All right. So we've got five different ways to do this, right, including the none case, which I don't want that.
We have two continuous cases.
And then we have three paging cases.
All right. So I'm going to walk through each one of these in kind here.
All right. Go to our orthogonal scrolling section behaviors demo.
And continuous is just like you'd expect it. This is a very simple, straightforward scroll view-like behavior, you know? You -- you had me at bounce, right? It's got that great -- gosh, I could do this for hours, right? All right. So that's just the normal scroll view behavior. But we have an additional one for this continuous, you know, fluid kind of scrolling that we call continuous group-leading boundary. And that's a mouthful but it's pretty descriptive.
So as we scroll and we impart volume to it, we naturally come to rest at that group's semantic-leading boundary.
So you people that clap have done targeted content offset or proposed offset before and you know it's a pain. All right. So with the addition of the continuous behaviors, we have some paging behaviors. And this gets really cool. So this is just like the normal scroll view paging behavior, and we named this behavior paging.
And here we can see no matter how much velocity the user imparts, we just get one page of content. And this page is defined as the default scroll view behavior where it's the width of the CollectionView.
We have two more variants to this, okay? And as you can probably intuit, we have this notion of group paging.
So now we have the semantic notion of what a group is, we can make the page size be the size of the group.
This gives you a really nice behavior.
Where you can automatically get that no occlusion thing going on, right, where your content's always front and centered.
And then the final one is group paging centered. It's just like group paging, but now we've centered that group for you automatically.
Yeah, it's pretty cool, right? And that gives you that great peekaboo effect, right, where you can see the content leading and trailing on the side, you know exactly what's going on.
All right, so that's a kind of an advanced tour of some of the additional features in compositional app. There's a lot more. Grab the SDK and check it out.
So at Apple, the notion of dealing with collaboration is super duper important to us. We have to work with other teams all over the company to solve problems.
And as a framework engineer, it's super duper important for us to deal all the different teams in the company to make sure we know what their needs are for new framework features.
And one of these teams, the App Store team had a new redesign coming up for iOS 13. They really wanted to simplify their code base quite a bit. So as part of that conversation, we talked about compositional layout. And they were real excited about it, went through a bunch of code. So for this, I'm going to bring up one of our adopters from the App Store team, Dersu Abolfathi.
Dersu? Dersu Abolfathi: Thank you, Steve.
The App Store is a destination for millions of customers looking for apps that help them get the most out of their devices.
Many of you will visit the App Store on a daily basis, so the content needs to be rich, engaging, and dynamic.
CollectionView plays a key role in delivering that experience.
So here's the App Store.
If you wanted to build UI like this today using just flow layout, you'd probably start with a CollectionView that scrolls in the vertical direction.
Then for every single section that scrolls on the horizontal axis, you'd need an additional CollectionView, which means more support code to intermediate the presentation and behavior of each collection.
With compositional layout, this can be done using just a single CollectionView. In fact, we've done just that.
In iOS 13, App Store has been rearchitected using compositional layout.
Each of the content types you see on this page are capable of vending their own layout description.
And all those layout sections come together to comprise our one overall CollectionViewLayout.
We describe this section using just a single layout item which has a known height and which occupies 100% of its container's width.
That layout item sits inside of a layout group, which itself occupies half of its container's width.
And these are really all the basic building blocks we need to get this UI working.
We take that layout group, stick it in a layout section, and to get that paging behavior we want, we set our orthogonal scroll behavior to group paging.
And we're off to the races.
In iOS 13 App Store is also getting UI support for languages that read from right to left. And compositional layout helps make that possible.
We construct our layouts using all the same primitives that you've seen here today, and compositional layout just takes care of the rest.
It ensures that the placement of our supplementaries and our cells is appropriate for our right to left environment.
And furthermore, we don't have to write a single line of code to make sure that our paging behavior also translates for the right to left layout direction.
So this new API has enabled to us take all of those scrollable regions that we were previously micromanaging and flatten them into a single CollectionView at the top level. All the while, our code has become more concise, easy to reason about, as well as easier to maintain going forward.
Compositional layout has reimagined the way that we think about CollectionViews in our own application, and we can't wait to see how it enhances the experience for the apps that all of you bring to the App Store. Back to you, Troy.
Troy Stephens: Thank you so much, Dersu. Boy, that is a beautiful redesign.
And we couldn't be more thrilled to see compositional layout already making a difference simplifying the development process for one of our prominent user-facing apps. And I'm so happy to be making the same API available to developers across our platforms.
Speaking of which, I'd like to show you a real quick demo of compositional layout working with NSCollectionView on Mac OS.
So when you open the sample project that you downloaded in Xcode, notice that there's a build scheme and target for the Mac.
And we're going to build and run that here.
And let's just open up all of our layout examples that we looked at in this talk, and we'll fan them out.
So here we go. Here they are, our various compositional layout examples.
And these are essentially just the same code that we use. We're programming to the same API's.
The only adjustments you're really going to see are for metrics where we wanted to apply more macOS-appropriate metrics for things. But we have orthogonal scrolling working here.
And, of course, on the Mac we expect things to behave in Mac-like ways. And one of the things that can happen with CollectionViews on the Mac is they're continually resizable usually, right? So we've got -- we've made sure that resize is nice and fast, super light-weight, these layouts reevaluate very quickly. And our adaptive sections layout has a layout break just like before. And now that you know how this works, you can easily add additional layout breaks for wider window widths and screen widths if you want.
Of course, we have features like being able to click items to select them.
I can use the arrow keys and navigate around.
I can hold down the shift key while arrowing around to accumulate a selection.
And, of course, I can drag to rubber band select. I can bulk select items. And this works in any old compositional layout, including more advanced ones like this.
So all the stuff that users would expect on the Mac. And we've got all these layouts running.
And enough of the code down below, the item section and group descriptions is in common in terms of the API that's used. You could even factor out code if you want and share code between your different platform projects if you want to.
So we can see that compositional layout is really here to make our lives a lot easier across our various platforms. And it really has a lot to recommend it.
It's available for you to start using today on iOS, tvOS, and macOS. And it makes it incredibly easy to create new custom layouts for CollectionView to use by simply describing it. We think this is a big game-changer.
This in turn makes CollectionView an even more versatile tool than ever before for presenting your content in arbitrary ways however you want to.
And the ease with which you can describe new layouts, and adjust layouts, and make different changes, and try different things, and iterate on them is going to enable much faster iteration with your designers.
So we think this is a really big game-changer.
So go out, take the sample project, experiment with it, try changing various things, use our code as a basis for yours and start working on the custom layouts for your next app versions.
And we can't wait to see the delightful user experiences that this is going to empower you to create in a fraction of the time.
Now, if you found this talk exciting, we've got another that I think you're really going to love, especially if you work with collection views or even UI table views.
In Advances in UI Data Sources we introduce an entirely new radically simpler API for feeding model data to your CollectionViews and your UI table views while getting automatic computation of differences and automatic animations for free. It's going to create delightful users experiences. So make sure to check this session out.
Thank you so much for watching.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.