Building Apps with Dynamic Type

Session 245 WWDC 2017

With Dynamic Type, people choose their preferred text size and iOS switches fonts automatically as needed. Understand why Dynamic Type is important and how to support it when displaying text. Learn what's new in iOS 11, and master the frameworks and tools that make it easy to support Dynamic Type in your app.

[ Applause ]

Hello everyone, and welcome to Building Apps with Dynamic Type.

My name is Clare, and together with my colleague Nandini, I'm excited to share with you how you can make your apps work great at any text size.

Today we'll first cover what Dynamic Type is and why the feature exists.

Next we'll look at what's new in iOS 11 for Dynamic Type.

We'll follow that with some guidelines and API you can apply to your own apps, and finally we'll show all of that hands-on in a sample app.

So first what is Dynamic Type.

Dynamic Type is a feature that allows users to customize the size of text on screen.

For example, some users may prefer a smaller text size so they can fit more content on screen.

Others may prefer a larger text size because it's more comfortable to read.

But Dynamic Type goes beyond a user's preferences.

Some users may experience eye strain when viewing text at the default size for long periods of time.

Others may simply have less acute vision due to aging.

And yet others may have a low vision condition which makes it impossible for them to read text at the default size.

So for these users Dynamic Type isn't just a preference, it's an actual need and by supporting Dynamic Type in your apps, you can allow these users to use your app and have a great experience with it.

The Dynamic Type settings can be found in the Settings app under Display and Brightness.

It starts out with seven different text sizes you can choose from, and the default one is in the middle.

You can also go into your Accessibility settings and there you can enable five even larger sizes.

Those sizes are geared towards users with low vision.

They range from somewhat larger than default size to much larger.

Now, looking at this you might be wondering whether you really need text to get that big.

Let's look in the example of why this is useful.

So here's the phone app and on the left we've got it at the largest size, not including the Accessibility sizes.

On the right we have it at the larger size overall.

Now let's look at this through the lens of a user with low vision.

Someone with low vision might see something closer to what we see here.

Notice how the version on the left is completely unreadable, but the version on the right is still fairly readable, even though its blurry.

So text size makes a huge difference for the legibility of your apps and that's why it's really important that you support the full range of Dynamic Type sizes.

So that's what Dynamic Type is, and now let's look at what's new in iOS 11 for Dynamic Type.

The biggest thing that's changed is that Dynamic Type support is vastly improved and expanded across all of iOS.

When we started working on iOS 11, we had three goals in mind.

First, text should be large enough for the user to read.

In other words, text should scale with Dynamic Type.

Second, the text should be fully readable.

It shouldn't be truncated unnecessarily and it shouldn't be overlapped or clipping.

And third, we want an app UI to look beautiful at all text sizes.

Now, for more information on the design thinking behind the iOS 11 Dynamic Type improvements, please check out the Design for Everyone talk from yesterday.

So now let's look at some examples of what's changed.

Here's the Settings app.

Notice how it now adapts beautifully for all the text sizes.

Here's another example from Calendar.

Notice how text everywhere is scaling with Dynamic Type, and some texts, like the event time, is laid out differently to accommodate larger text.

Now, there are some cases where we don't scale for Dynamic Type.

For example, in tab bars, if we were to scale a text here, the tab bar would take away too much room from the main content.

However, now, in iOS 11, if you have one of the five largest Dynamic Type settings chosen, you can long press on one of the items in the tab bar.

For example, the Voicemail tab, and that will show a larger version in the middle of the screen.

So that's a sampling of what's changed in iOS 11.

Now let's look at some guidelines in API that you can apply to your own apps.

We'll be covering this in four areas.

First, we'll look at how you can scale your font sizes for Dynamic Type.

Second, we'll see how you can accommodate larger text in your app's layouts.

Third, we'll look at the specific, very common case of table views.

And then finally, we'll even see how images can work well with Dynamic Type.

So first, let's scale font sizes for Dynamic Type.

The easiest way to do this is to use the built-in text styles that come with iOS.

Here's a list of all of our text styles, along with their font sizes at the default text size.

If your user has a different text size selected, these font sizes will also change.

Now, for those of you who have already be using text styles in your app, first, thank you; and second, there's one important change in iOS 11 that you should be aware of.

Prior to iOS 11 all the text style adapted for the seven standard sizes.

However, only the body style adapted for the five accessibility sizes.

In iOS 11 all of the text style adapt for all 12 text sizes.

And so if you're using text styles, you'll get support for all 12 sizes for free.

To use text styles in Interface Builder, go to the Attribute Inspector and change the font on your view.

By default this will be something like System 17 point, which means it's not going to scale with Dynamic Type.

But if you click on that T icon, you can choose one of the text style fonts instead and those will scale.

Also, new in iOS 11 you can check the Automatically Adjust Font checkbox.

This means that if the user changes their text size while your app is running, your view will automatically update its font for the new text size.

So we highly recommend you check this on all of your views.

In code it's just as easy to use text styles.

You can use the preferredFont(forTextStyle) API to get a font for the text style, and then you can use the adjustsFontForContentSize Category property to make sure that your views update their fonts if the text size changes.

But what if you can't use text styles?

Maybe you have a design that requires custom fonts.

The great news is that in iOS 11 it's really easy to get those fonts to work well with Dynamic Type.

So let's say you have some code that assigns the font property of a label to a custom font of your choosing.

In iOS 11 you can now use the new UIFontMetrics class to scale that font so that it will get smaller or bigger, depending on the user's text size.

[ Applause ]

Thanks.

[ Applause ]

In this example we're using the default font metrics, which means we're using the font metrics of the body text style.

That means that if body text would be 50 percent larger for a particular user, your font will also get 50 percent larger.

For better results, you can pick a particular text style that you want to scale your font with.

So, for example, if you have a font that you're using for title text in your app, you might choose to scale it in the same way that we've scaled title1 text.

Now, a final word on web views.

Many of you may have web views that you're displaying in your iOS app.

You should make those adapt for Dynamic Type as well.

To do so, you can use the -apple-system-body font and that will get you the body text style font and it will adapt for the user's dynamic typesetting.

Now, note that this is only available on Apple devices, so if you're planning to use the same web page for other platforms, make sure you set an appropriate fallback font.

Then for your other styles you can define their font sizes in terms of relative units, and that way they'll scale proportionately.

So that's how you can scale your font sizes for Dynamic Type.

Now let's look at how you can accommodate larger text in your app layouts.

So let's say we've got some text at the default text size, and you can imagine that the white border is the available area on the screen.

So first step, let's scale the font size.

We have a problem.

The text is now running off screen and so your users are not going to see anything outside of the white box.

To prevent the label from running off screen, we can add a trailing constraint.

But now we still have a problem because the text is getting truncated.

The end result is that your users aren't going to see all the text.

To really fix this let's wrap the text and that will allow the text to grow as much as it needs while still displaying all of the text.

To wrap text in Interface Builder you can use the Attribute Inspector and adjust the Lines property on your label.

By default this is set to one line, but if you change it to 0, that will allow your label to wrap to as many lines as it needs for its content.

You can do the same thing in code by setting the number of lines property to 0.

Okay. Let's look at another common scenario that can occur.

So when you're writing your app and you're thinking about the default text size, it's tempting to use constant values for your spacings.

So you might have something like two labels here whose baselines are separated by a constant 40 points.

Looks great at the default text size, but once you start adapting for Dynamic Type, you can run into problems like this, where the labels overlap.

What we really want here is a spacing that's dynamic and that accounts for the fonts involved.

In iOS 11 you can now do this with new Auto Layout System Spacing Constraints.

You can define the spacing between two baselines and Auto Layout will automatically figure out the best spacing to use based on the fonts of your viewers.

Now, if you try this and you decide that you want a slightly larger or smaller spacing, you can do that, too, by adjusting the Multiplier property and that will let you get the exact results you want.

It's also possible to use these new Auto Layout Constraints in Interface Builder, and for more information on that, please check out the Auto Layout Techniques and Interface Builder talk that happened this morning.

Now, I know there are probably some of you out there who have a view in your app that was written eight or nine years ago and it's just not using Auto Layout.

Don't worry, we have you covered.

So let's say you have some layout code that sets a constant value and ads that to a y origin.

Using the same UIFontMetrics class we saw earlier we can scale that 40 point value, or any CGFloat value, and that way it will get smaller or larger, depending on the user's text size.

Now let's look at one final common scenario.

Here we've got two labels that are side-by-side at the default text size.

Again, you can imagine that the yellow border is the area available on screen.

Now let's scale our font sizes.

As you can see, there's not enough room for the text anymore, so going back to what we saw earlier, we could try to wrap the text and we'll get something like this.

So I'd like to step back for a second and revisit those three goals we mentioned at the beginning of the talk.

First, all text should be large enough to read.

We've got that covered.

Second, all text should be fully readable.

That's true here.

None of the text is missing.

Third, app UI should look beautiful at all text sizes.

And that's where we haven't quite met our goals yet.

As you can see, on the righthand side the d is being orphaned from the rest of the word and it just doesn't look right.

So for this scenario we recommend that for larger font sizes you change your layout to use vertical stacking and that way each label can grow to the full width of the screen.

Now, this technique works not just for text, but for anything that might take room away from text.

For example, here's the Siri Help screen and notice how on the left, at the default text size, there's an icon that takes up a horizontal column away from the text.

So that's why for larger font sizes we actually move that icon above the text and allow the text to grow to the full width of the screen.

Now, to do an alternate layout based on the user's text size you'll need to know how to get that.

You can do that by accessing the preferredConstantSizeCategory property on either your view's traitCollection or on the UIApplication object.

The default text size will return large and the standard sizes run from extra small to extra, extra, extra large.

The Accessibility sizes are prefixed with Accessibility to avoid adding more extras.

Now, in iOS 11 we've added new functions that you can use to ask questions about the user's text size.

So, for example, you can check whether the user is using one of the Accessibility text sizes, and if so, you can do an alternate layout like vertical stacking, or you can use any threshold you want.

We now support compares in operators so you can check whether the user's text size is larger than a particular threshold.

So that covers some general guidelines for adapting your layouts for Dynamic Type.

Now let's look at the specific case of table views.

Earlier we saw how the Settings app adapts beautifully for all Dynamic Type sizes.

The good news is that if you're using a standard table view styles, you will get this kind of layout for free.

So your fonts will grow or shrink, depending on the Dynamic Type setting, and for larger fonts the text will wrap so that all of it is visible.

Also, for standard table views that have a detail text label, those will automatically lay out with vertical stacking for larger fonts.

So standard table views will adapt their layout for Dynamic Type, and cell heights are based on their content.

This is particularly important at larger font sizes where the text wraps because a cell that has more text may need to be taller than a cell that has less.

So how do we accomplish variable cell heights in a table view?

We can do this through a technique called self-sizing.

When self-sizing is enabled, the table view will ask each cell to provide its own size and the cell can calculate that based on its own content.

Then the table view client simply needs to give the table view an estimate for any cells that are off screen.

In iOS 11 self-sizing is enabled by default.

However, if you're using fixed row heights or if you are using Interface Builder, you may need to enable self-sizing.

To do so set your row height of a table view to be Automatic Dimension and then set the Estimated Row Height to a reasonable estimate given your table view.

If you have section headers and footers, you can do the same thing for those using very similarly named properties.

So that's the story for standard table view cells.

Now, if you're using custom cells with Auto Layout, it's really easy to get these to work with self-sizing.

All you need to do is set up your constraints so that they define the size of the cell and then Auto Layout will figure out the size for you.

So let's look at how we could set up the constraints for this custom cell shown here, where we have two labels, one on top of the other.

We'll definitely want leading and trailing constraints for those labels.

And we want to make sure that the labels are separated vertically by an appropriate amount of space.

Again, you can use the new System Spacing Constraints to get a value that will work well at all text sizes.

So are we done?

Not quite.

We still need to add constraints relative to the top and bottom of the cell and that will complete the set of constraints we need to define the size of the cell.

Again, you can use the System Spacing Constraints here to get values that will look good across all text sizes.

Now, if you're using Manual Layout with your cells, you can also use self-sizing.

To do so you'll want to override the sizeThatFits method on your cell, and then you can use the contentView's width to figure out the width available for your subviews.

Now let's move on to our last topic, images.

Why are images showing up in a talk about Dynamic Type?

After all, they're not text.

But images are an integral part of your app's UI and in some cases they carry meaning that's not communicated elsewhere.

So for the same reason it's important for your text to be readable by your users, it's also important that your images are distinguishable.

Let's look at an example.

So here's the Recents view in the phone app.

Notice how in front of some of the calls there's an image of a phone with an arrow pointing out of it.

This image means that this call was made by you versus a call that came in.

Now let's turn up the text size.

For those of you who are wondering, the image has moved over here and notice how it is tiny compared to the text.

If you need your text to be this big so you can read it, you probably can't see that image.

So what we really want to do here is scale the image the same way that we scale a text, and that's exactly what we've done in iOS 11, and better yet, we made it easy for you to do so in your own apps.

So to just allow your images to scale for Dynamic Type you'll first want to make sure that your images are provided as PDF's at 1x scale.

So this is a good idea anyways because it allows you to use the same image for both 2x and 3x resolution devices.

In fact, your app might already be doing this.

So just make sure you have a PDF.

Next, go to the Asset Catalog and check this new checkbox for Preserving Vector Data.

If you don't check the checkbox, when the Asset Catalog is compiled, it's going to rasterize all the PDF's.

But if you check the checkbox, the original PDF gets preserved, and the cool thing about that is that now if you use this image and put it into an image view of any size, the image will get drawn smoothly using PDF Drawing.

[ Applause ]

Now, the final step is to make sure that your view resizes for Dynamic Type.

To do this in Interface Builder you can select any image view or button and you can check the new Adjusts Image Size property.

This means that your image viewer button will scale up for the five largest Dynamic Type sizes.

And you can do the same thing in code with the Adjusts Image Size for AccessibilityContentSizeCategory property.

There's one more place where image scaling comes in handy, and that's when the user long presses on a bar item.

So earlier we saw how you can long press on the Voicemail tab to show a larger version in the middle of the screen.

Notice that both the text is scaling and also the image.

So we want to make sure that that image looks as good as possible.

If your bar item is using a PDF image, all you need to do is check the Preserve Vector Data checkbox that we saw earlier.

That will allow UIKit to scale it up smoothly.

If you're not using a PDF, you'll want to provide a larger version that we can show when the user long presses on that image.

To do that in Interface Builder you can go to the Attributes Inspector and set the Accessibility property of your bar item to be the larger version.

Or in code you can simply set the largeContentSizeImage property to that larger version.

So we've just covered a variety of tips and tricks you can use to adapt your apps for Dynamic Type.

And now I'd like to invite Nandini to the stage to show all of that in a sample app.

[ Applause ]

Hi everyone.

I'm Nandini.

I'm a software engineer in the Accessibility Team at Apple.

Today, using a sample app, I'll show you how you can fix your app in very little time with [inaudible] font sizes.

First, I will open the app in the default text size, and later I'll open the app in the largest of the Dynamic Type font size and fix issues along the way.

Let's start the app.

We cleared it.

It's called Cute Battle Pets.

In this app you can choose among two pets to battle with each other.

Let me open the app now.

In this tab, Opponents, you see a list of pets you can choose to battle among each other, and the next tab, Achievements, you see a list of badges of honors these pets can earn.

Before auditing our app for the largest of the Dynamic Type font size, if we switch back to the Opponents tab, you can audit your app for the largest of the Dynamic Font size using a very handy tool in Xcode called Accessibility Inspector.

This tool is very handy because you can audit your app before going to the Settings app on your device.

You can open this tool by going to Xcode in the menu bar, choose Open Developer Tool and then click on Accessibility Inspector.

This tool can be used to audit apps in all macOS and iOS iPhones, including the Simulators.

As our app is running on the iPhone Simulator, I'll choose Start from the dropdown menu.

I'll go to the Settings tab in the Accessibility Inspector.

Here you see a font size slider.

The font size is currently the default text size.

I'm going to move the slider's knob to the last notch on the slider.

That is the largest of the Dynamic Type font size.

Let's go back to our app now to see the effect of this change.

The font size of these labels has not changed yet.

Any idea why this is happening?

This is happening because of two reasons.

The labels here are not yet set to automatically adjust to the user's ContentSizeCategory, and the petName and petDescription labels here are using custom fonts.

Let's go to a project now to fix these issues.

I'll minimize Accessibility Inspector for now.

I'll go to BattleCell zip file.

Here, using the jump bar, I'll move to Set Up Labels and Buttons Method.

Here for petName, petDescription and battleButton's titleLabel, I'll set the property at this font for ContentSizeCategory and I'll set it to True.

As petName and petDescription are using custom fonts, we can use UIFont, make it API, scan font for font metric.

For petName I'll be using the headline TextStyle.

For petDescription I'll be using the sub headline TextStyle.

Let's run the app again and see the effect of these changes.

Now, after these labels are adjusted to the user's ContentSizeCategory, we see that these labels have really less horizontal room to grow.

With the menu on the left and the button on the right, these labels have really less horizontal room to grow and the labels either truncate or wrap in multiple lines.

We can fix this by having an alternate adopt layout for larger text constraints.

We can achieve this by moving the image above the text and the button below the text.

This will ensure that the labels are less of wrap and truncated.

Let's go fix this in our project now.

First, before adding this adoptLayoutConstraints for larger text constraints, let me add a private variable for that.

Next let me go to setupLayoutConstraints method.

Here you see that you already have a set of layout constraints for default text size.

Now I'll be placing a set of constraints for larger Dynamic Type font sizes.

These set of constraints pasted here will be available in the sample app code attached along the session website.

You can go and review this after the session, but I give a short overview of what this actually do.

These constraints ensure that the image labels and the button are vertically stacked, and these constraints also get them in the height of the cell.

Okay. Let's go to our developerLayoutConstraints method.

Here we see that one of the developer constraints aren't activated.

We need the larger [inaudible], right.

Let's fix that.

Here, based on user's preferredContentSizeCategory, they put in the five largest Dynamic Type font sizes.

With the app you will be able to look at its constraints and activateLargeTextConstraints.

Otherwise, do the reverse operation.

To ensure that these layout constraints are up-to-date based on the change in trait collection, we call this method if trait collection did change.

If the user's preferredContentSizeCategory shifts from the five largest Dynamic Type font sizes to the smaller Dynamic Type font sizes, or visa-versa, these layout constraints will be updated.

Let's run the app again to see the effect of these changes.

Great. After vertically stacking the content the labels are less of wrapped and there's no truncation at all.

Let's go the next step, Achievements.

When we run the app in the default text size, you will notice that these cells here have multiple lines of text, but currently only one line of text is visible on screen.

Let's go to Achievements View Controller in Swift check on why this is happening.

This is happening because the table view's row height is set to a constant value, hundred.

This makes that these cells do not have cell size at all.

To fix this and make this cells have size we have to set the table view's row height to use UITableViewAutomaticDimension, and TableView's estimatedRowHeight to a constant value, and I'll choose hundred.

Let's run the app again.

Let me go to Achievements tab.

Now we see that we have more lines of text in each of these cells, and the cells, they're sized to fit content, but let's take a look at the [inaudible].

Unlike the labels the images do not grow, according to the user's ContentSizeCategory.

To fix this let's go to Achievements [inaudible] file.

And set the label and image view method.

For the badgeImageView I set the property adjustsImageSize for accessibilityContentSize Category, and I set it to two.

This will ensure that the image is scaled for the five largest Dynamic Type font sizes.

Let's run the app again to see the effect of this change.

Let me move to Achievements tab.

Great. Both the labels and the image have sizes largest Dynamic Type font size, but let's take a closer look at the image in these cells.

Let's also take a look at the large text only that comes up in a long press on the tab bar icons.

Based on that, the images in these cells and also in the tab bar icons were not smooth.

To fix that we have to go to Assets Catalog, choose our images, those are PDF images, go to the Attributes Inspector and click on the Preserve Vector Data checkbox.

If we do this, what happens is the PDF vector data is preserved and the images are rendered smooth in different Dynamic Type font sizes.

Let's run the app again to see the effect of this change.

Let me go to Achievements tab.

For confirmation, let me zoom in again.

Now we see that the images here are rendered smooth, right.

Finally, using the Accessibility Inspector, I'm going to run through a quick audit of this app and different Dynamic Type font sizes.

For that let me minimize Xcode.

Let me bring up the Accessibility Inspector.

Let me move the first tab, Opponents.

Let me switch the font size slider to default text size.

Now I'm going to increase the font size one-by-one and you'll notice that the labels in these cells increase according to the user's ContentSizeCategory.

When I move to the five larger Dynamic Type font sizes, the layout of these cells change.

The labels have more horizontal room to grow and the cell says sizeToFitContent.

Next move on to Achievements to do the same audit.

I'll switch back to default text size.

I'll increase the font size one-by-one.

Now, you see that the labels wrap in multiple lines.

The cell says sizeToFitContent, and also the image scales in the five larger Dynamic Type font sizes.

So there you have it.

Our app looks pretty great in all the Dynamic Type font sizes.

So using the new API's available in iOS 11, you can make your apps, too, work for all Dynamic Type font sizes.

Now I welcome Clare back on stage to conclude the presentation.

Thank you.

[ Applause ]

Thanks, Nandini.

So all of that sample code is available on our session website, but the version we have on the session website is actually expanded to include even more examples.

So you can see how we do this in Interface Builder.

You can see an example where we wrap text around images.

You can find a case where we allow scrolling only when necessary for larger fonts, and more.

Think of it as a recipe book for things you might encounter in your own apps.

So we highly encourage you to check it out after the session.

To summarize what we covered today, it's easy to support Dynamic Type with new API in iOS 11.

If you have an app that doesn't support Dynamic Type, now is a great time to do so, and you should because supporting Dynamic Type is good for your users.

It's good for the users who prefer smaller text because it allows them to see more on screen.

It's good for the users who prefer larger text because it's more comfortable to read, and it's especially beneficial for users who need larger text in order to be able to read it.

When more apps are usable by people with a disability, such as low vision, it really helps to level the playing field for those users.

And so by supporting Dynamic Type in your app, you're actually making the world a better place.

So we really hope you take what you saw today in today's session and go back and make apps that work well with Dynamic Type and include more users so that more users can benefit from the great work you've done.

For more information, including the sample code we showed, please go to our session website.

Now, there have been several sessions at this conference related to the one we just gave.

As we mentioned earlier, the Design for Everyone talk from yesterday covers the design thinking behind the iOS 11 Dynamic Type improvements.

There are also two sessions on accessibility, What's New in Accessibility and Media and Gaming Accessibility.

Both of these sessions cover how you can widen your user base and include more users.

And finally, as we mentioned earlier, to learn more about the System Spacing Constraints in Interface Builder, check out the Auto Layout Techniques in Interface Builder talk.

Thank you all for coming, and I hope you enjoy the rest of the conference.

[ Applause ]

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