↓ Skip to main content

Tracking Interface Orientation in SwiftUI

·579 words·3 mins
Igor Skvortsov
Author
Igor Skvortsov

The short guide on how to track the interface orientation in SwiftUI.

Task
#

Track the interface orientation for the current view in order to implement layout correctly.

Solution
#

Orientation tracking class:

public final class IsPortraitOrientationBroadcaster: ObservableObject {

    public static let shared = IsPortraitOrientationBroadcaster()
    @Published var isInterfacePortrait: Bool = true

    private var interfaceOrientation: UIInterfaceOrientation? {
        (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.interfaceOrientation
    }

    private init() {
        // Set initial orientation based on the interface orientation
        if let interfaceOrientation {
            isInterfacePortrait = interfaceOrientation.isPortrait
        }

        // Observe orientation changes based triggered by device orientation change
        NotificationCenter.default.addObserver(
            forName: UIDevice.orientationDidChangeNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            guard let interfaceOrientation = self?.interfaceOrientation else { return }
            self?.isInterfacePortrait = interfaceOrientation.isPortrait
        }
    }
}

Property wrapper for convenient use:

@propertyWrapper public struct IsPortraitOrientation: DynamicProperty {
    @ObservedObject private var broadcaster: IsPortraitOrientationBroadcaster = .shared

    public init() {}

    public var wrappedValue: Bool {
        broadcaster.isInterfacePortrait
    }
}

How to Apply
#

You can now easily use this Property Wrapper in SwiftUI to manage the interface based on orientation:

struct ContentView: View {
    @IsPortraitOrientation var isPortrait

    var body: some View {
        Text(isPortrait ? "Portrait" : "Album")
    }
}

What’s Happening Here?
#

Orientation
#

When tracking device orientation in iOS, it’s important to understand the difference between UIWindowScene.interfaceOrientation and UIDeviceOrientation.

UIWindowScene.interfaceOrientation
#

What It Is: The interface orientation of the app, reflecting the current position of the interface relative to the device screen.

How to obtain it: UIApplication.shared.windows.first?.windowScene?.interfaceOrientation Note that there can be multiple scenes in an app, so the orientation must be used for the specific scene.

When Available: Available in the context of the active window of the app (scene), rather than the entire device.

Pros

  • Always available right after the app starts, which is useful if you need to ensure the correct interface state from the very beginning
  • Reflects the current state of the interface and is used to manage interface changes when the orientation switches (for example, switching between portrait and landscape mode)
  • Remains stable if the interface orientation is locked, regardless of the physical orientation of the device

Be aware

  • May not match the interface orientation if, for example, the interface is locked in portrait orientation but the device is in landscape orientation

UIDeviceOrientation
#

What It Is: The physical orientation of the device, tracked by system hardware sensors.

How to obtain it: UIDevice.current.orientation

When Available: Always available, but there are nuances regarding how quickly it updates.

Pros

  • Reflects the physical position of the device. Useful in cases where it’s important to account for the actual movements of the device, such as when working with the camera.

Be aware

  • After the app starts, it may return a value of .unknown, as the orientation only updates after a physical change in device position. Therefore, it’s not always reliable at app launch
  • May not match the interface orientation if, for example, the interface is locked in portrait orientation but the device is in landscape orientation

Important Note
#

There are examples on the internet of mapping UIDeviceOrientation to UIInterfaceOrientation and vice versa. This approach can lead to inconsistencies, especially if the interface is locked or if data about the physical orientation of the device has not yet updated.

Property Wrapper
#

Using a Property Wrapper is not mandatory but simplifies the process, making the code more concise and convenient. In this example, @IsPortraitOrientation automatically tracks orientation changes, saving you from manually updating the state.

Related Links #