Dynamic Type: Large Content Viewer

Dynamic Type: Large Content Viewer

What is the Large Content Viewer?

Large Content Viewer is a feature introduced by Apple along Dynamic Type.

Most of the time, your content should grow according to the current Dynamic Type Size. But for some components, it can make sense to keep them at a default (small) size.

For example, components that are always on screen, such as status bars and tab bars.
Or components that are secondary to the content, such as navigation bars and toolbars.

Examples of element that always stay small. From left to right: tab bar, status bar, toolbar and navigation bar.
Examples of element that always stay small no matter the selected Dynamic Type Size

They should be kept small in order to leave the available screen space to the primary content.

But we still need to make sure those components stay readable.
For that, Large Content Viewer comes into place!

All Dynamic Type Sizes with the 5 biggest ones which are considered as accessibility sizes highlighted.

If one of the 5 Accessibility Dynamic Type Sizes is selected → when the user puts his finger on an element such as a tab bar or navigation bar → a popup (or “Large Content Viewer”) is displayed:

Large Content Viewer interaction on tab bar and navigation bar

When should we use Large Content Viewer on our custom components?

It will depend, but generally on cases similar to Apple’s:

  • When a component is always visible but is secondary to the main content.
  • When a component has navigation purposes.

I’ll show you examples for both cases we have in our app at Immoweb.

Custom tabs

Custom tabs that supports the Large Content Viewer feature

In some places, we use custom tabs on top of the screen. It acts as a secondary navigation (the main one being the bottom tab bar). We mimick the behavior of the native tab bar since it is the same type of component.

Search bar/button inside navigation bar

Search bar/button that supports the Large Content Viewer feature

In the homepage and search page, we display a search bar (which is actually just a button which opens the filters screen) inside a navigation bar. So we need to recreate the “Large Content Viewer” behavior of the navigation bar to display its content in large when an accessibility Dynamic Type Size is selected.

Navigation bar extension that supports the Large Content Viewer feature

Another example is the navigation bar “extension” that provides secondary information/actions to the search screen. Making it bigger according to the Dynamic Type Size would take the space needed for the list of results. And since it’s “glued” to the navigation bar, it makes sense to follow the behavior of the navigation bar, keeping it small.

How can we implement the Large Content Viewer?

UILargeContentViewerItem protocol

Since iOS 13, Apple introduced a new protocol: UILargeContentViewerItem. And every UIView implements this protocol. It has 5 properties. The 3 first ones speak for themselves:

The 3 main properties: showsLargeContentViewer, largeContentTitle and largeContentImage

The last 2 properties are:

  • scalesLargeContentImage: Bool: whether the image should be scaled to a larger size appropriate for the Large Content Viewer. If false, it will show the image at its intrinsic size.
  • largeContentImageInsets: UIEdgeInsets: appropriate insets for positioning the image in the Large Content Viewer so that it appears visually centered.

By default, views like UILabel and UIButton will have a largeContentTitleand a largeContentImage based on their existing text and image properties.


UILargeContentViewerInteraction interaction

So in order to display a Large Content Viewer for a certain view, you need to:

You can do it easily by writing:

myView.showsLargeContentViewer = true
myView.addInteraction(UILargeContentViewerInteraction())

Note:
If you have a group of components you should add the interaction to the view grouping those components.

In the example of the tab bar we saw above: it’s the tab bar itself that has the UILargeContentViewerInteraction and each tabs that sets their showsLargeContentViewer to true.

Doing that allows the user to slide his finger from one tab to the next and see the Large Content Viewer updates with the info of the tab currently underneath his finger. This behavior is not available if it’s the same view who implements showsLargeContentViewer and the interaction.

When the user lifts his finger up while a Large Content Viewer was displayed, Apple triggers a touchUpInside event on the active item. So UIButton’s will be automatically triggered. But for custom components, we’ll need to implement a delegate:

myView.addInteraction(UILargeContentViewerInteraction(delegate: self))

UILargeContentViewerInteractionDelegate delegate

Custom tabs

The custom tabs at the top of a page are actually cells inside a collection view. So the default touchUpInside trigger via the Large Content Viewer will do nothing here. We can overcome this issue by using the UILargeContentViewerInteractionDelegate like so:

class TopTabBarController: UIViewController {
	// ...
	
	override func viewDidLoad() {
		super.viewDidLoad()

		if #available(iOS 13.0, *) {
			tabBarCollectionView.addInteraction(UILargeContentViewerInteraction(delegate: self))
		}
	}
	
	// ...
	
	func configureCell(_ tabCell: UICollectionViewCell, info: Info) {
		if #available(iOS 13.0, *) {
			tabCell.showsLargeContentViewer = true
			tabCell.largeContentTitle = info.title
		}
	}
}

extension TopTabBarController: UILargeContentViewerInteractionDelegate {
	@available(iOS 13.0, *)
	func largeContentViewerInteraction(
		_ interaction: UILargeContentViewerInteraction,
		didEndOn item: UILargeContentViewerItem?,
		at point: CGPoint
	) {
		guard let index = tabBarCollectionView?.indexPathForItem(at: point) else {
			return
		}

		// We call `collectionView(_,didSelectItemAt:)` function to trigger existing code
		collectionView(tabBarCollectionView, didSelectItemAt: index)
	}
}

Here we are:

  • Adding the UILargeContentViewerInteraction interaction to the collection view
  • Setting the TopTabBarController as the delegate of the interaction
  • Setting each cell to show the large content viewer and setting their titles
  • Implementing UILargeContentViewerInteractionDelegate to trigger the existing function collectionView(_, didSelectItemAt:)

And now we have our custom tabs with the correct behavior 🙌

Custom tabs that supports the Large Content Viewer feature

I am no expert but I’m happy to help you in case you need some help 🙃
Feel free to reach out to me via Twitter!

Special thanks to SeLoger mobile team to have pointed out to me this accessibility feature that we were not yet using in our app.

I hope you liked the article.

See you soon for more adventures 👋

Resources

Special thanks to Mohamed Louli and Vincent Martin for proofreading this article 🙏