I introduced myself to the world of dynamic text sizes back in August 2019. My country’s popular food delivery apps didn’t change the text size when you changed the system font size. (they still don’t) I wondered what APIs are required to increase the text sizes and alter the view accordingly.

In SwiftUI, using the system font text style automatically changes:

Text("Something cool goes in here.")
  .font(.body)

It also adjusts the view and the constraints accordingly when the size increases or decreases. But, as you increase the text size to the accessibility sizes (huge text), the view may truncate and look unreadable. You may’ve to create another different view structure to solve this.

Old Way - ContentSizeCategory

SwiftUI provides us with an environment ContentSizeCategory. Here’s an example from my app where I change the layout from a grid to a list when the size increases:

struct HomeGameTypeView: View {
  @EnvironmentObject var preferences: Preferences
  
  @Environment(\.sizeCategory) private var category
  
  var body: some View {
    if category >= .extraExtraLarge {
      ScrollView {
        LazyVStack {
          list
        }
      }
    } else {
      LazyVGrid(columns: [.init(), .init()]) {
        list
      }
    }
  }
  
  var list: some View {
    ForEach(GameTypeOption.allCases) { type in
      HomeGameTypeRow(selection: $preferences.gameTypeSelection, type: type)
    }
  }
}

New Way - DynamicTypeSize

In iOS 15, the ContentSizeCategory is deprecated and replaced by DynamicTypeSize. Rewriting the above example:

struct HomeGameTypeView: View {
  @EnvironmentObject var preferences: Preferences
  
  @Environment(\.dynamicTypeSize) var size // <- NEW
  
  var body: some View {
    if size >= .xxLarge { // <- NEW
      ScrollView {
        LazyVStack {
          list
        }
      }
    } else {
      LazyVGrid(columns: [.init(), .init()]) {
        list
      }
    }
  }
}

This can also limit the text sizes to a particular size (not recommended, in my opinion) or a range of sizes.

var content: some View {
  Text(item.name.uppercased())
    .font(.title)
    .dynamicTypeSize(..<DynamicTypeSize.accessibility1) // <- RANGE
    .foregroundColor(.primary)
}

I know that the title text style is large enough, and I don’t want it to go beyond the accessibility medium size.

DynamicTypeSize in Previews

To test different sizes in previews, you can add a constant size as an environment to the preview:

struct IntroductionView_Previews: PreviewProvider {
  static var previews: some View {
    IntroductionView()
      .environment(\.dynamicTypeSize, .accessibility1) // <- CONSTANT
  }
}

Large Content Viewer

If you’re constraining the text size, you can add the method accessibilityShowsLargeContentViewer(). When the user is on an accessibility text size, a long tap on the view shows an enlarged text size:

var body: some View {
  Button(action: action) {
    content
  }
  .buttonStyle(HomeButtonStyle(isSelected: false))
  .padding([.bottom, .horizontal], 8)
  .accessibilityShowsLargeContentViewer()
}

Although, note these words from the documentation:

Rely on the large content viewer only when items must remain small due to unavoidable design constraints. For example, buttons in a tab bar remain small to leave more room for the main app content.

Don’t use the large content viewer to replace proper Dynamic Type support. For example, Dynamic Type allows items in a list to grow or shrink vertically to accommodate the user’s preferred font size. Rely on the large content viewer only when items must remain small due to unavoidable design constraints.

Conclusion

Dynamic Type helps your users use your app for any text size, providing them with an exceptional experience. Using SwiftUI makes it easier to add Dynamic Type and structure the views accordingly.

I know it gets tougher as the views get more complex, but you can still try your best to make your apps accessible for at least xxxlarge sizes.

If you have a better approach, tag @rudrankriyam on Twitter!