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()
}
}
}
Either with the built-in isPresented
or with a custom environment value you can now detect the user interface level.