Hey ;-),
my app supports the portrait mode at the moment. And I want to implement the landscape mode as well.
2 questions regarding device orientation detection:
- What is the best way to detect the orientation of a device? A solution that is working on app launch too (not only on a rotation of the device)
At the moment I'm testing:
@Environment(\.verticalSizeClass) private var verticalSizeClass
But it seems that the devices I'm using for tests (simulators and a real device) do not support upside down mode. In the deployment info of my app I choose all 4 orientations (portrait, upside down, landscape left and right). Still the upside down does not redrawn the app and the orientation detected by "verticalSizeClass" says: compact
- Is this a normal behavior (ignoring upside down mode) for all devices?
Thanks in advance, Jackson
To add to the suggestion @Jackson-G
- To specify a supportedOrientations, you could would need to use a UIKit app to create a fixed-orientation UINavigationController, then interface with SwiftUI via UIViewControllerRepresentable.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
let navigationController = SupportedOrientationHostingController(rootView: ContentView())
window.rootViewController = navigationController
self.window = window
window.makeKeyAndVisible()
}
}
}
struct SupportedOrientationsPreferenceKey: PreferenceKey {
static var defaultValue: UIInterfaceOrientationMask = .allButUpsideDown
static func reduce(value: inout UIInterfaceOrientationMask, nextValue: () -> UIInterfaceOrientationMask) {
value = nextValue()
}
}
final class SupportedOrientationHostingController<Content: View>: UIHostingController<SupportedOrientationHostingController.Root<Content>> {
var orientations: Box!
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
orientations.supportedOrientations
}
init(device: UIDevice = .current, rootView: Content) {
let box = Box(device: device)
let orientationRoot = Root(contentView: rootView, box: box)
super.init(rootView: orientationRoot)
self.orientations = box
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
final class Box {
var supportedOrientations: UIInterfaceOrientationMask
init(device: UIDevice) {
self.supportedOrientations = device.userInterfaceIdiom == .pad ? .all : .allButUpsideDown
}
}
struct Root<Content: View>: View {
@State private var currentOrientation: UIDeviceOrientation = UIDevice.current.orientation
private let orientationPublisher = NotificationCenter.Publisher(
center: NotificationCenter.default,
name: UIDevice.orientationDidChangeNotification
)
let contentView: Content
let box: Box
var body: some View {
contentView
.onPreferenceChange(SupportedOrientationsPreferenceKey.self) {
self.box.supportedOrientations = $0
}
.onReceive(orientationPublisher) { _ in
self.currentOrientation = UIDevice.current.orientation
}
.environment(\.deviceOrientation, currentOrientation)
}
}
}
extension View {
/// Sets the supported interface orientations for this view and its ancestors.
/// - Parameters:
/// - orientations: The interface orientations that the view supports.
/// - sizeClasses: The user interface size classes that this view uses to determine layout.
/// - Returns: A view that supports the specified interface orientations.
func supportedOrientations(_ orientations: UIInterfaceOrientationMask, sizeClasses: [UserInterfaceSizeClass?] = .all) -> some View {
// Propagate the requested orientations up the view hierarchy:
preference(key: SupportedOrientationsPreferenceKey.self, value: orientations)
}
}
extension Collection where Element == UserInterfaceSizeClass? {
static var all: [UserInterfaceSizeClass?] {
[.compact, .regular]
}
}
To also observe the orientations changes, you could define an EnvironmentValues such that,
struct DeviceOrientationKey: EnvironmentKey {
static let defaultValue: UIDeviceOrientation = .unknown
}
extension EnvironmentValues {
var deviceOrientation: UIDeviceOrientation {
get { self[DeviceOrientationKey.self] }
set { self[DeviceOrientationKey.self] = newValue }
}
}
struct ContentView: View {
@Environment(\.deviceOrientation) private var deviceOrientation
var body: some View {
Text("Hello, world!")
.onChange(of: deviceOrientation) {
dump(deviceOrientation)
}
.supportedOrientations(.landscape)
}
}