UIUserInterfaceLevel in SwiftUI

UIUserInterfaceLevel in SwiftUI

Β·

3 min read

iOS 13 introduced a new interface environment trait called UIUserInterfaceLevel. It describes the visual level of the content indicating if the interface is base (equivalent to your window's main content) or elevated. Elevated describes content visually above your window's main content.

Levels create a visual separation between different parts of your UI. Window content typically appears at the UIUserInterfaceLevel.base level. When you want parts of your UI to stand out from the underlying background, assign the UIUserInterfaceLevel.elevated level to them. For example, the system assigns the UIUserInterfaceLevel.elevated level to alerts and popovers.

In UIKit the level can be obtained through UITraitCollection and its instance property userInterfaceLevel.

UITraitCollection.current.userInterfaceLevel

But where to look in SwiftUI to get this information?

A good start is looking in the environment (using the property wrapper @Environment). Certain traits can be read using an appropriate EnvironmentValues key path. For example, you can read the color scheme of the current view using the key path of the colorScheme property.

@Environment(\.colorScheme) var colorScheme: ColorScheme

And then you can access the property in your view's body.

if colorScheme == .dark { // Checks the wrapped value.
    DarkContent()
} else {
    LightContent()
}

The following key paths are available related to display characteristics.

  • colorScheme
  • colorSchemeContrast
  • displayScale
  • horizontalSizeClass
  • imageScale
  • pixelLength
  • verticalSizeClass
  • widgetFamily

Hmmn, nothing related to UIUserInterfaceLevel. Wait a minute. I see isPresented in a different section of the documentation. isPresented is a boolean value that indicates whether the view associated with this environment is currently presented.

Can isPresented be used instead? I guess so. The property returns true for views presented as modals or for alerts.

As a safe alternative, it is possible to provide a custom environment key/value to read and return the current UIUserInterfaceLevel. Let's define the custom EnvironmentKey and its defaultValue is a computed property reading from the current UITraitCollection.

import UIKit

struct UserInterfaceLevel: EnvironmentKey {
    static var defaultValue: UIUserInterfaceLevel {
        return UITraitCollection.current.userInterfaceLevel
    }
}

A custom, computed property on EnvironmentValues is needed to return the UIUserInterfaceLevel.

extension EnvironmentValues {
    var userInterfaceLevel: UIUserInterfaceLevel {
        get { self[UserInterfaceLevel.self] }
    }
}

Let's put it into a SwiftUI View

struct LevelInfoText: View {
    @Environment(\.isPresented) private var isPresented
    @Environment(\.userInterfaceLevel) private var userInterfaceLevel

    var body: some View {
        VStack {
            Text(isPresented ? "Presented" : "Not presented")
            Text(userInterfaceLevel == .base ? "Base" : "Elevated")
        }
    }
}

And embed this view in another view with the capability to present itself as modal.

struct ContentView: View {

    @State private var showModal = false

    var body: some View {
        VStack {
            LevelInfoText()

            Button("Show modal") {
                self.showModal = true
            }
        }.sheet(isPresented: $showModal, onDismiss: {
            print(self.showModal)
        }) {
            ContentView()
        }
    }
}

Example

Either with the built-in isPresented or with a custom environment value you can now detect the user interface level.

Did you find this article valuable?

Support Marco Eidinger by becoming a sponsor. Any amount is appreciated!