Large content viewer in SwiftUI

0
20

iPhone users can adjust preferred text sizes in their device settings. There are 7 text sizes available by default from extra small to extra-extra-extra large. Users can also enable the Larger Text accessibility setting to get access to five larger accessibility sizes. People with low vision rely on accessibility text sizes to make app content readable for them. When we use system fonts and symbol images in our apps we get dynamic type size support automatically.

But not all UI scales with dynamic type size by default. Elements in toolbars, navigation bars and tab bars usually don’t grow to leave as much space as possible for the main content in the app.

#

Large content viewer in system components

Let’s look at an example app with a tab bar showing a map and the current weather in the user’s location. We can see that only the text in the main area adjusts to match the size setting. The buttons in the tab bar keep their original size.

Screenshots of the sample app with a map and a tab bar in extra large text size and accessibility size 5

When we use the built-in SwiftUI TabView component, people with low vision can read the content in the bar by long pressing on the buttons. This feature is called large content viewer and it’s automatically activated for system toolbars when one of the accessibility text sizes is set. The user can swipe on the bar to get to the next item, and when they let go the button is considered tapped.

Screenshots of the sample app with large content viewer activated for the tab bar

#

Large content viewer in custom components

If we have custom toolbar style elements in the app that should not grow for larger accessibility text sizes, we have to do a bit of extra work. Imagine that we wanted to add a floating button that re-centers the map on the user’s location. We don’t want the button to grow too big and obscure the contents of the map, so we’ll limit its dynamic type size to xxxLarge.

LocationButton()
    .dynamicTypeSize(...DynamicTypeSize.xxxLarge)
Screenshot of the sample app with a location button added in the top trailing corner of the map

Now the button will scale up to the extra-extra-extra large size but won’t grow any bigger when one of the accessibility sizes is activated. It’s great for browsing the map but not great for users who struggle to see the smaller button when using accessibility text sizes.

Luckily, we can easily add support for large content viewer to our custom elements by applying the accessibilityShowsLargeContentViewer() modifier in SwiftUI.

LocationButton()
    .dynamicTypeSize(...DynamicTypeSize.xxxLarge)
    .accessibilityShowsLargeContentViewer()

When the user long presses on the button they will be able to preview it in the large content viewer if they have one of the larger accessibility text sizes turned on on their device.

Screenshot of the sample app with the large content viewer activated for the location button

By default the large content viewer will present the button exactly how it appears on screen, in our case it’s just the symbol image. We can customize what is shown in the viewer by providing a custom view to the accessibilityShowsLargeContentViewer(_:) modifier. For example, we might want to show an image and a text label instead.

LocationButton()
    .dynamicTypeSize(...DynamicTypeSize.xxxLarge)
    .accessibilityShowsLargeContentViewer 
        Label("Recenter", systemImage: "location")
    
Screenshot of the sample app with the large content viewer activated for the location button showing the symbol and the text label

#

Large content viewer and long press handler

We might have a custom control that is activated by a long press gesture but should also show the large content viewer in accessibility sizes. We have to ensure that users have access to both the preview and the regular long press action, so we have to modify the minimum duration of the long press when the large content viewer is enabled.

We can read the accessibilityLargeContentViewerEnabled setting from the environment in SwiftUI and base the gesture duration on the value.

We’ll add a quick weather preview to the map, similar to what the Maps app system has. When the user long presses on the button we’ll show a weather overview in their location. We also don’t want this button to grow too big to give as much space as possible to the map, so we’ll limit the dynamic type support on the button label.

WeatherButtonLabel()
    .dynamicTypeSize(...DynamicTypeSize.xxxLarge)
Screenshot of the sample app with the weather button in the bottom trailing corner of the map

In order to support both the large content viewer and the long press gesture on the button, we’ll increase the minimum duration of the gesture if the large content viewer is enabled.

struct MapView: View 
    ...

    @Environment(.accessibilityLargeContentViewerEnabled)
    var largeContentViewerEnabled
    
    var body: some View 
        Map(coordinateRegion: $region)
            .overlay(alignment: .bottomTrailing) 
                WeatherButtonLabel()
                    .dynamicTypeSize(...DynamicTypeSize.xxxLarge)
                    .accessibilityShowsLargeContentViewer()
                    .onLongPressGesture(
                        minimumDuration: largeContentViewerEnabled ? 2 : 0.5
                    ) 
                        showWeatherOverlay = true
                    
            
    

With this setup the user with an accessibility text size preference will have 2 seconds to preview the button before the long press is activated and the weather overlay is shown.

Screenshots of the sample app with the large content viewer shown for the weather button on the first one and the weather overlay on the second one

While the large content viewer APIs are really useful for some of UI elements like we’ve seen in our examples, it’s important to prioritize dynamic type support throughout the app and only fallback to the large content viewer when it’s truly required by design.

You can find the code for the sample app shown in this post in our GitHub repository.

If you are enjoying our blog and would like to support us, you can now sponsor us on GitHub.

For updates about the blog and development tips, follow us on Twitter @nilcoalescing.

Source

LEAVE A REPLY

Please enter your comment!
Please enter your name here