iOS 7 Dynamic Type

June 29, 2013

iOS 7 introduces Dynamic Type, which if apps choose to support it, gives the user control over text size in an app. Web pages have had this feature for free since the early days of the web, but web browsers moved away from dynamic text sizing. The Two Types of Browser Zoom.

iOS7 Dynamic Type automatically adjusts weight, letter spacing, and line height for every font size. It looks stunning, but there’s work to make your app support it.

Default Size

Here’s what UserXListView looks like with the Text Size (Settings - General - Text Size) set to the default size in the iPhone Retina (3.5-inch) iOS Simulator.

Text Size Default

We get 3 and ‘a bit’ cells in the UITableView.

UserXListView Default

Smallest Size

Changing the Text Size to the smallest size means we can fit slightly more.

Text Size Smallest

Nearly 4 cells in the UITableView.

UserXListView Smallest

Largest Size

Changing the Text Size to the largest size means we can fit slightly less, but get big beautiful type.

Text Size Largest

3 cells in the UITableView.

UserXListView Largest

Implementation, The Easy Part

That’s how it looks, but how hard is it to make your app support it. Let’s look at UserXListView. The cell layout makes use of Auto Layout Constraints, which are very powerful.

UserXListView Cell

iOS 7 has defined Text Styles - Body, Caption 1, Caption 2, Footnote, Headline 1, Headline 2, Subhead 1, and Subhead 2. We assign a Text Style to each label in the cell layout via the attribute inspector as shown below.

UserXListView Cell Label Attribute Inspector

Once the label Text Styles and Auto Layout Constraints are set, we’re ready for the hard part.

Implementation, The Hard Part

Unlike Auto Layout in this example, there’s more work to do to support Dynamic Type.

We need to make the Controller aware of a Text Size change by hooking into the UIContentSizeCategoryDidChangeNotification.

- (void)viewDidLoad {
  [super viewDidLoad];
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(preferredContentSizeChanged:) name:UIContentSizeCategoryDidChangeNotification object:nil];
}

- (void)preferredContentSizeChanged:(NSNotification *)aNotification {
  // adjust the layout of the cells
  [self.view setNeedsLayout];

  // refresh view...
}

We need to calculate the height of the row/cell based on it’s content, and as each label in the cell can change height we need to calculate the total height of the cell required.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  Account *account = [self.fetchedResultsController objectAtIndexPath:indexPath];

  UIFont *nameLabelFont = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline1];
  CGSize nameLabelFontSize = [[account name] sizeWithAttributes:[NSDictionary dictionaryWithObject:nameLabelFont forKey:NSFontAttributeName]];

  // ...

  CGFloat PADDING_OUTER = 10;
  CGFloat totalHeight = PADDING_OUTER + nameLabelFontSize.height + ... + PADDING_OUTER;

  return totalHeight;
}

We also need to programmatically assign (or re-assign because we already did it in the Storyboard) the cell label Text Style values.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  AccountCell *cell = (AccountCell *)[tableView dequeueReusableCellWithIdentifier:@"AccountCell"];

  Account *account = [self.fetchedResultsController objectAtIndexPath:indexPath];

  cell.nameLabel.text = [account name];
  cell.nameLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline1];

  // ...
}

Final Thoughts

That’s it. iOS 7 determines and applies the font attributes for each label based on the Text Style for each label, and the iOS 7 Text Size system setting selected by the user, and it’s left to us (the developer) to cater for the changes in the layout to the view.

As you can see there’s a bit of extra work to support Dynamic Type. It would be nice if it was a bit smarter, a bit more automatic, but with great power comes great responsibility.