Discover new techniques for configuring collection view and table view cells to quickly build dynamic interfaces in your app. Explore configuration types you can use to easily populate cells with content and apply common styles. Take advantage of powerful APIs to customize the appearance of cells for different states. Find out about patterns and best practices that simplify your code, eliminate bugs, and improve performance.
Welcome to "Modern Cell Configuration." I'm Tyler Fox, an engineer on the UIKit team. In iOS 14, we're bringing brand-new features to UICollectionView by building on top of foundational technologies, which can be broadly organized into three categories: the way that you populate data, the way that you define your layout and the way that you display content. Check out the "Advances in Collection View" session from WWDC 20 for an introduction to all of these new features.
In this session, we're going to be focusing on View Configuration, new APIs to configure the content and styling of your cells.
We'll start by covering the basics and seeing how easy it is to use these new configurations to set up your cells.
Next, we'll dive into a new concept called "configuration state," which works together with configurations to make it easy to get different appearances for different states.
Finally, we'll take a closer look at the two configuration types, background configurations and content configurations, and cover using those in more detail. Before we get into any of these modern cell configuration APIs, let's take a quick look at an example of how you would configure a table view cell in iOS 13.
If you've ever used UITableView before, this should look very familiar. Here, we're using the built-in imageView and textLabel properties on UITableViewCell to display an image and some text. This is a simple example of what we mean by cell configuration. With that in mind, let's take a look at how you would do the same thing using the new configuration APIs.
This is how you configure a cell using a content configuration. It looks pretty similar to what we had before, but with two new lines of code. Let's break it down. The first thing that we do is ask the cell for a defaultContentConfiguration. This always returns a fresh configuration without any content set on it. But this configuration does have default styling, based on the cell and table view style.
We'll talk more about the styling and other features of content configurations shortly.
For now, we'll just set the image and text on the content configuration. This part should feel very familiar. Now that we've set up our content, there's one final step.
We apply the configuration to the cell by setting it to the contentConfiguration property. As soon as we do this, the cell is updated to display the image and text that we specified.
Now, before this line of code, setting the image and text only changed our local copy of the configuration stored in this "content" variable. Because we're using a content configuration, we never directly touch a UIImageView or UILabel. All of the properties are set on the configuration itself. So, what have we gained by using a content configuration to set up our cell? Well, for one, the code that you see here to configure a table view cell is actually the same code you would use to configure any cell, such as a collection view cell. In fact, the same code works for any view that supports content configurations, even ones that aren't cells, such as table view headers and footers. How is this possible? This works because configurations are composable.
Instead of all of the functionality being baked into the cell class itself, like it was with UITableViewCell, these standard cell layouts and appearances are now available as independent pieces that can plug right into any cell or view that supports them.
Let's run this code to see how it looks and learn what other benefits we get from using configurations.
There's our cell, inside of a collection view list, using the new sidebar appearance for multi-column iPad apps.
You can see that the cell has the star image and text that we configured.
Now, things get more interesting when we start to interact with this cell. When I touch down on the cell, it shows the highlighted appearance. And when I lift up, the cell becomes selected.
But these are just a couple of the different states that the cell can be in, so let's add some more cells and manually put them into a bunch of different states to see how the cell's appearance changes. Here's the same content configuration, but applied to all of these different cells in different states. Let me swap out the text in these cells to indicate which state each cell is in.
As you can see here, the default styling for a sidebar list cell varies significantly in different states. By using configurations to set up our cell, we get all of these different appearances automatically. We'll talk more about how this works in a bit.
So, we've gotten a taste of these new configurations, but what are they exactly? A configuration describes the appearance of a view for a specific state: things like the content, styling, metrics and behavior.
By itself, a configuration is just a bunch of properties. It doesn't do anything until you apply it to a view or a cell to render.
And as we've already discussed, configurations are composable: they can be used with any type of cell or view that supports them. Now, there are two types of configurations, background and content configurations. Let's look at some of the features that these two system configurations provide.
Background configurations have a number of properties to let you quickly specify common background appearances. With just a couple lines of code, you can customize the background fill color, set a visual effect to get a nice blur and add a stroke and some rounded corners.
And if you want to do something more custom, you can do that by providing your own view.
List content configurations give you the standard layouts for cells, headers and footers, much like the familiar UITableViewCell styles you have today.
But list content configurations are even more powerful. They support an image, text and optional secondary text and expose many properties for you to customize for each of those.
They also offer higher-level behaviors, such as flexible layouts to display larger amounts of text and special layout modes for Accessibility text sizes.
These two configuration types go hand in hand and have some common design principles.
One of the most important principles is that configurations are lightweight and very inexpensive to create. They're value types in Swift, which means whenever you have a configuration, it's yours alone, and changes that you make to your configuration don't affect anything else until you set that configuration to the cell.
Because configurations are lightweight, you should always start with a fresh configuration, such as by asking the cell for its defaultContentConfiguration as you see here.
This applies not only the first time you configure the cell, but also whenever you want to update a cell that has already been configured. Each time, just start with a fresh configuration and set it up for the new state. When you're using configurations, you don't need to think about the old state at all. Don't worry about whether an old configuration was already applied, and you shouldn't try to get the existing configuration first to make changes to it. Just start with a fresh configuration each time, set it up the way you want and then apply it to the cell.
If you've been using UIKit for a long time, this might feel a bit different at first. But once you start thinking this way, it's incredibly liberating. When you apply a configuration to the cell, UIKit is going to do all of the heavy lifting for you. It will figure out what's changed and efficiently update the views as needed. As you saw when we ran the code earlier, configurations give you the default appearance for many different states. But configurations are also built on top of robust infrastructure that you can use to customize the appearance for these states, which we'll talk about in more detail shortly.
Configurations also give you access to advanced behaviors in just a couple lines of code. Let's say you want to animate a change to your background appearance. All you have to do is set a new background configuration inside an animation.
Configurations aren't just easy to use, their design actually eliminates entire classes of bugs, especially when you're dealing with complex states and transitions. You always know that the currently applied configuration is the truth, and when you set a new configuration, that new configuration is applied all at once.
Finally, configurations are built from the ground up for performance, which is especially important to ensure smooth scrolling.
Because UIKit is responsible for managing the views and rendering, we're able to implement many internal performance optimizations under the hood, which you get for free. Now that you're familiar with the basics of configurations, let's talk about a new concept that works together with them, called configuration state.
Configuration state represents the various inputs that you use to configure your cells and views. What kind of inputs are we talking about? One of the most common inputs that determines how you set up your views are traits from UITraitCollection. These are things like the size class, user interface style and the content size category. On top of those traits, your cells and views exist in a variety of different states. Maybe your cell is selected or is a target for drag and drop or maybe it's temporarily disabled. These are just some of the different states that are common in UIKit. And on top of these states, your app has its own custom states. These are all of the different pieces of state that you use which are specific to your app and its domain. For example, when configuring cells, a messaging app might need to know whether a message is archived or flagged, and a payment app might show which transactions are processed. If you use a view model to populate your cells with content, you can also think of that view model as a custom state as well. All of these things are what make up configuration state: a collection of all the different traits, states and your own custom states, wrapped together in one place.
So, where do you find a configuration state? Each cell, header and footer in your collection or table view has its own configuration state. There are three cells shown here, and each one might have different things in its configuration state.
So, what does a configuration state look like? Well, there are two different types, as you can see here. There's a configuration state type for views, like this header, and there's a different type for cells.
Here's what a view configuration state looks like. It starts with a trait collection and then we have four different states-- highlighted, selected, disabled and focused. These are just simple Booleans.
And finally, we have optional custom states. This is key-value storage for you to add any extra states or data that you want to use when configuring your view.
So, that's view configuration state. What about cell configuration state? Well, cell configuration state just takes everything from view configuration state and adds some additional states that are specific to cells, whether the cell is editing, swiped or expanded, as well as some states specific to drag and drop.
One of the most important things you can do with configuration state is use it to update configurations for new states. You can ask any background or content configuration to return an updated version of itself for a different configuration state. This will return a new copy of the configuration, with its properties updated to reflect the new state.
For example, a background configuration might change its background color, and a content configuration might change the image tint color and text color.
When you ask for an updated configuration, the original configuration doesn't change. And if you customized any of the properties on the original configuration, those properties will remain the same on the updated configuration. You can think of the properties on a configuration as becoming "locked" once you've set them. Recall the demo from the beginning of this session, where we saw our content configuration updating its appearance for different states.
That worked because of automatic configuration updates.
By default, when you set a background or content configuration on the cell, any time the cell's configuration state changes, it will automatically ask the configuration to return an updated version of itself and then reapply that new configuration back to the cell.
These properties let you control this behavior.
Now, automatic updates are great to get the default styling for each state, but if you want to customize the appearance for different states, you can disable automatic updates and update the configurations yourself instead.
But where should you put your code that updates these configurations? Well, we have a brand-new method on collection and table view cells, called updateConfiguration(using state.
This is a method for you to override in your cell subclass, where you can put your code to configure the cell based on the state that's passed in.
This method is always called before your cell first displays and will be called again any time the configuration state may have changed, so you can configure yourself for the new state.
Remember, when you're using configurations, you don't need to worry about the old state. Just get a fresh configuration each time, set its properties and apply it to your cell.
Configurations work best when there's a single place in your code that handles setting them up and applying them to your cell.
If you're using a custom cell subclass, this new method, updateConfiguration(using state, is the best place to do that. This method is also a great place to centralize any other setup or updates to your cell.
For example, if you're using the new collection view list cell, you can use this method to update the tint color of your cell accessories for different states.
And if you need to reconfigure your cell for any reason, just call setNeedsUpdateConfiguration to request an update.
Let's take a look at an example of how to use this method to set up our cell and manually update configurations for different states.
First, we'll get a fresh configuration by asking the cell for its default content configuration. And we'll immediately update that for the new state.
This is going to give us a configuration with the system default styling for this state.
Next, we'll set the configuration's image and text, for the item that our cell is displaying.
And now we're ready to apply our customizations. Here, we're checking to see if the state is highlighted or selected, and if it is, we're setting the image and text colors to white.
For any other states, we don't set the colors at all, which means we're using the configuration's default colors.
Finally, we set our new configuration to the cell. And that's it. This method will get called again any time the state changes and will apply a new configuration for the new state each time.
So this is how you customize the content configuration's appearance for different states. We could similarly customize the background configuration here as well.
Now in this example, we set different colors for the image and text in certain states.
But there's another way to change the appearance of colors in different states using a new type called a color transformer. A color transformer takes in one color and returns a different color by modifying the original color in some way.
For example, you might have a color transformer that returns a grayscale version of a color.
So a color transformer is just a simple function. But color transformers are very powerful, because you can produce many different variants from the same input color with different color transformers.
This is how color transformers can be used to create different appearances for different states. Some of the default configurations for certain styles and states will have a preset color transformer, in order to produce a specific appearance for that state. The color transformer combines with the input color to produce the resolved color that you actually see.
Now that you understand how configurations can produce updated appearances for different states, let's cover some of the details that you need to know when using background and content configurations.
Collection view list cells, table view cells and table view headers and footers all set up a default background configuration automatically, based on the style of the containing list or table view.
So typically, you won't need to do anything at all to get the background appearance you want. Now, content configurations work a little differently. Instead of the cell automatically applying one by default, you can use the defaultContent Configuration method on the cell to get a fresh configuration based on the cell's style, as you've seen in the earlier examples.
But for both background and content configurations, you can easily request the default configuration for any style that you'd like by going directly to the UIBackgroundConfiguration and UIListContentConfiguration types. Here you can see examples of how to do this for a sidebar style list cell. And there are similar methods for the other different styles of cells, headers and footers.
How about we take a look at some of these different styles in action? Here we have a list of sticker categories. This list is currently configured with the sidebar appearance, so all of our cells have default background and content configurations that match that style. You can see the cells update their background and content appearance for different states as I interact with them.
But the sidebar style is just one of the appearances that we can use for our collection view list.
Let's take a look at what happens if we configure this with some different appearances.
Here's the same list, configured with the grouped appearance. And this is the inset grouped appearance.
And here's the plain appearance. You probably recognize these styles from UITableView.
Collection view list supports some new styles, such as this sidebar plain appearance and the original sidebar appearance that we started with. All of the visual styling that you see for these different list appearances is coming from the default background and content configurations.
Now let's switch back to the group style, so we can see the individual cells more clearly and take a look at how they respond to different dynamic type sizes by changing the system text size setting.
You can see that by default, everything responds dynamically to these changes. As we increase the text size, the entire layout adjusts to accommodate that.
And if we switch on the largest Accessibility text sizes, you'll see that the cell layouts use a special mode where the text wraps around the image in order to maximize the space available for the content.
Content configurations are built from the ground up to support dynamic layouts like this. So let's take a quick look under the hood at how that works.
Content configurations are designed to be used with self-sizing cells, where their height can be flexible, depending on the exact configuration and environment.
This is important to ensure that your app looks great when running on different device sizes, at different dynamic type sizes and with different amounts and types of content. Content configurations give you control over the layout margins, shown in blue here, and various padding properties, shown in orange. Together with the actual content, these are what determine the intrinsic height and affect how the cell self-sizes. Instead of trying to enforce a fixed height, you should use these properties on the content configuration to influence the layout and allow the height to adjust dynamically. There's one more layout concept that you should know about. Let's see how it works in an example. Here we have three different cells, each using a content configuration with an image and some text. Right now, everything looks pretty good. But say we want to use some different images in these cells.
The problem is that the images aren't exactly the same size. They have slightly different widths. So the images and the text aren't aligned across cells anymore. The reason that this happened is because the images are leading aligned, and the text is positioned with the same amount of padding from the trailing edge of each image. To get the right alignment across these cells, we need to specify what we call a reserved layout size for each image.
If we set the same reserved layout width for the image on each content configuration, what that does is horizontally center the image inside that reserved amount of space. In this illustration, the distance between the two red-dotted lines is the reserved layout width for each image. The reserved layout size does not affect the actual size of the image. And if the image is larger than the reserved layout size, it will be allowed to extend outside of that area. Using a reserved layout size for the image also correctly aligns the text, because the text is positioned relative to the same reserved layout area for the image in each cell. If you're using symbol images, UIKit applies a standard reserved layout size automatically, which you can request manually for non-symbol images if needed. So that's what you need to know about list content configuration layout.
Let's go over a few important details to keep in mind as you start adopting configurations.
If you have existing code that you're updating and migrating over, keep in mind that configurations are mutually exclusive with some existing properties.
Setting a background configuration always resets the background color and background view properties to nil. And the same thing applies the other way around.
So make sure that you don't mix a background configuration with other code still setting these other background properties on the same cell.
And specifically for those of you using UITableView, content configurations supersede the built-in subviews of cells, headers and footers, like the imageView, textLabel and detailTextLabel.
These legacy content properties will be deprecated in a future release, so we encourage you to adopt content configurations to take advantage of their more powerful features and enhanced customizability. The background configuration and list content configuration types that we've covered today are incredibly powerful. But there are still going to be times when you need to do something more custom.
With configurations, you have more options than ever before.
In addition to the list content configuration, we're also giving you access to the associated list content view, which implements all of the rendering.
You just create or update this view using the configuration, and then you can add it as a subview right alongside your own custom views.
This lets you take advantage of all the content configuration features and combine the list content view with your own additional custom views next to it, such as an extra image view or a label.
And because the list content view is just a regular UIView, you can actually use it by itself anywhere, even outside of a collection or table view, such as in a plain UIStackView.
But what about cases when you aren't building a list at all, or want to do something completely custom? Configurations have got you covered.
Even when you're building a completely custom view hierarchy inside your cells, you can still use the system configurations to help.
Because configurations are so lightweight, you can use them as a source of default values for things like fonts, colors and margins that you copy over to your custom views, even if you never apply the configuration directly itself.
And for more advanced use cases, you can create a completely custom content configuration type with a paired content view class that renders it, and then use your custom configuration with any cell the same way that you would use a list content configuration.
With a custom configuration, you can take advantage of all the features that we've talked about, including allowing your custom configuration to automatically update itself for new states. No matter how complex or custom your cells are, you'll be able to take advantage of the power of configurations. We've covered all the essentials of modern cell configuration.
You've seen how easy it is to use background and content configurations and how you can use the same code to configure a collection view cell or a table view cell.
Configurations let you focus on what you want to display without worrying about how to update all of your views. You just start with a fresh configuration each time, set it up the way you want and apply it to your cell, letting UIKit take care of the rest.
You've seen how configurations can be updated using configuration state to produce a wide variety of different appearances and how you can use configuration state when setting up your cells.
And everything that we've talked about today is designed to be composable and extensible to meet your needs.
So go download the sample app and give these new APIs a try, then start using content and background configurations to set up collection and table view cells in your app.
Be sure to check out the collection view sessions from WWDC 20 to learn more about building lists
and much, much more.