Dynamic Type

Dynamic Type

Curtis just spent the last two weeks updating his app Slopes to support Dynamic Type. You might have heard of UIFontMetrics, or you might not have. But you probably didn’t sweat the pixels to make sure you top and bottom margins were baseline-height appropriate ;). He’ll share how he went about adapting to various sizes within dynamic type, and tricks to better mimic the behavior of built-in UI classes (like TableViews).

9b54e5324785eb939bcc8f15c724baf9?s=128

Curtis Herbert

March 14, 2019
Tweet

Transcript

  1. Dynamic Type @parrots

  2. Dynamic Type @parrots

  3. Dynamic Type @parrots

  4. One of the many things you know you should do,

    but haven’t gotten to yet. • Dynamic Type • Localization • Voiceover
  5. None
  6. None
  7. None
  8. None
  9. ~30% of Slopes users use a non- standard font setting.

  10. Auto Layout Everywhere Interface Builder First About Slopes

  11. Easier than ever!

  12. iOS 7: Classes of fonts (“Body”, “Header”, etc) iOS 9:

    More classes (“Callout”, “Title 1”, “Title 2”)
  13. 7 Sizes

  14. iOS 10: adjustsFontForContentSizeCategory

  15. iOS 11: 5 “Accessibility” Sizes

  16. None
  17. None
  18. iOS 11: UIFontMetrics

  19. OK, So, How-do?

  20. Use a system font size (body / etc) Enable adjustsFontForContentSizeCategory

    Automagic
  21. Doesn’t work with bold

  22. let metrics = UIFontMetrics.init(forTextStyle: UIFont.TextStyle.callout) label.font = metrics.scaledFont(for: UIFont.systemFont(ofSize: 16.0))

    label. adjustsFontForContentSizeCategory = true Automagic - Custom Font
  23. viewDidLoad() { … let metrics = UIFontMetrics.init(forTextStyle: UIFont.TextStyle.callout) label.font =

    metrics.scaledFont(for: label.font) label. adjustsFontForContentSizeCategory = true Automagic - Custom Font in IB
  24. Only do this once per label otherwise

  25. UIApplication.shared.preferredContentSizeCategory traitCollection.preferredContentSizeCategory Not Just for Fonts

  26. if UIApplication.shared.preferredContentSizeCategory >= .accessibilityMedium { return 2.5 } else if

    UIApplication.shared.preferredContentSizeCategory >= .extraLarge { return 1.2 } else if UIApplication.shared.preferredContentSizeCategory >= .extraExtraLarge { return 1.8 } return 1.0 Not Just for Fonts
  27. let metrics = UIFontMetrics(forTextStyle: UIFont.TextStyle.footnote) let graphLineWidth: CGFloat = metrics.scaledValue(for:

    1.0) Not Just for Fonts
  28. Won’t automatically update

  29. Change graph height Change stroke widths

  30. Planning for Expansion

  31. None
  32. None
  33. Planning for…

  34. Planning for Expansion

  35. None
  36. None
  37. func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { super.traitCollectionDidChange(previousTraitCollection) if previousTraitCollection?.preferredContentSizeCategory != traitCollection.preferredContentSizeCategory

    { //update whatever is needed } } Responding to Changes
  38. UIContentSizeCategoryDidChangeNotification Responding to Changes

  39. WWDC 2017 #245: Building Apps with Dynamic Type

  40. Tip 1: Accessibility Inspector

  41. None
  42. Tip 2: Something is better than nothing.

  43. Don’t even have to do every size all at once.

  44. 80 70%

  45. class SlopesApplication: UIApplication { @objc dynamic override var preferredContentSizeCategory: UIContentSizeCategory

    { get { if super.preferredContentSizeCategory >= .accessibilityMedium { return UIContentSizeCategory.extraExtraExtraLarge } return super.preferredContentSizeCategory } } } Limit Size via UIApplication
  46. Don’t even have to do every screen all at once.

  47. Tip 3: Use system components They just work!

  48. None
  49. Self-Sizing Cells:

  50. override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat

    { return UITableView.automaticDimension }
  51. Built-in Sizes

  52. … most of the time

  53. Tip 4: Know Thy Sizes

  54. UIFontMetrics.init(forTextStyle: UIFont.TextStyle.body)

  55. None
  56. Grows Grows Shrinks Doesn’t Shrink

  57. func autoSize() { let bodyFontMetrics = UIFontMetrics(forTextStyle: .body) for label

    in manualResizeBodyLabels ?? [] { label.font = bodyFontMetrics.scaledFont(for: label.font) label.adjustsFontForContentSizeCategory = true } let footnoteFontMetrics = UIFontMetrics(forTextStyle: .footnote) for label in manualResizeFootnoteLabels ?? [] { label.font = footnoteFontMetrics.scaledFont(for: label.font) label.adjustsFontForContentSizeCategory = true } …
  58. Tip 5: Plan for Alternate Layouts AKA Stack Views, Stack

    Views everywhere
  59. if UIApplication.shared.preferredContentSizeCategory >= .accessibilityMedium { stack.axis = .vertical stack.spacing =

    8.0 } else { stack.axis = .horizontal stack.spacing = 24.0 }
  60. None
  61. None
  62. None
  63. Tip 6: Doesn’t have to be pretty, especially at accessibility

    sizes
  64. Use maximums where they make sense .scaledFont(for: label.font, maximumPointSize: 40.0)

  65. Tip 7: Pay Attention To Baselines Especially in UITableViews

  66. My Text Their Text

  67. My Text Their Text Top = Top + 8 Bottom

    = Bottom + 8
  68. None
  69. My Text Their Text My Text Their Text

  70. My Text Their Text My Text Their Text Spacing relative

    to baseline
  71. label.firstBaselineAnchor.constraintEqualToSystemSpacingBelow( contentView.to pAnchor, multiplier: 1.0) contentView.bottomAnchor.constraintEqualToSystemSpacingBelow( label.lastBa selineAnchor, multiplier: 1.0)

  72. None
  73. No specified height

  74. None
  75. None
  76. How’d it go?

  77. None
  78. 2 weeks

  79. Thank You @parrots