SwiftUI - Determining Current Device and Orientation

  • Thank you for the code!!! I just add:

         else if horizontalSizeClass == .compact && verticalSizeClass == .compact {               Text("iPhone compact")

    for small devices!

Add a Comment

Apple Recommended

Replies

Ultimately you'll probably have to drop down to UIKit to use the UIDevice API. You can opt to receive notifications when it changes, and can use an environment object to watch these:


final class OrientationInfo: ObservableObject {
    enum Orientation {
        case portrait
        case landscape
    }
    
    @Published var orientation: Orientation
    
    private var _observer: NSObjectProtocol?
    
    init() {
        // fairly arbitrary starting value for 'flat' orientations
        if UIDevice.current.orientation.isLandscape {
            self.orientation = .landscape
        }
        else {
            self.orientation = .portrait
        }
        
        // unowned self because we unregister before self becomes invalid
        _observer = NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification, object: nil, queue: nil) { [unowned self] note in
            guard let device = note.object as? UIDevice else {
                return
            }
            if device.orientation.isPortrait {
                self.orientation = .portrait
            }
            else if device.orientation.isLandscape {
                self.orientation = .landscape
            }
        }
    }
    
    deinit {
        if let observer = _observer {
            NotificationCenter.default.removeObserver(observer)
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var orientationInfo: OrientationInfo
    
    var body: some View {
        Text("Orientation is '\(orientationInfo.orientation == .portrait ? "portrait" : "landscape")'")
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(OrientationInfo())
    }
}
  • Do you really need code in deinit? Then deallocated the class is unsubscribed automatically.

  • You saved my live! The Apple's way only works after rotation... Yours works from the app's start! 🥳

  • This solution does not deal with the 'flat' device orientations

Add a Comment

Thanks Jim. I guess this will suffice for now, I'll probably continue with sizeClasses for iPhone in general and drop to this UIKit solution for iPad when necessary.

You can check for portrait/landscape like this:

Code Block swift
GeometryReader { geometry in
if geometry.size.height > geometry.size.width {
print("portrait")
} else {
print("landscape")
}

That won't tell you what device you are on.
But, that will work if you want to handle wide windows differently from tall windows on an iPad, too. (Or probably on macOS, too. I haven't tested that, though. That's an exercise left for the reader.)


  • Smart, best solution for me !

Add a Comment
Combine based version of @Jim's answer:


Code Block swift
final class DeviceOrientation: ObservableObject {
enum Orientation {
        case portrait
        case landscape
    }
    @Published var orientation: Orientation
    private var listener: AnyCancellable?
    init() {
        orientation = UIDevice.current.orientation.isLandscape ? .landscape : .portrait
        listener = NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)
            .compactMap { ($0.object as? UIDevice)?.orientation }
            .compactMap { deviceOrientation -> Orientation? in
                if deviceOrientation.isPortrait {
                    return .portrait
                } else if deviceOrientation.isLandscape {
                    return .landscape
                } else {
                    return nil
                }
            }
            .assign(to: \.orientation, on: self)
    }
    deinit {
        listener?.cancel()
    }
}


Hello! I found another possible solution. Let me know if it works for you:

import SwiftUI
import Combine

struct OrientationView: View {
        
    @State private var orientation: UIDeviceOrientation? = nil
    @State private var orientationChangePublisher: AnyCancellable?
    
    var body: some View {
        Text("Hello, World!")
            .onAppear {
                orientationChangePublisher = NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)
                    .compactMap { notification in
                        UIDevice.current.orientation
                    }
                    .sink { newOrientation in
                        orientation = newOrientation
                        print("isLandscape: \(orientation?.isLandscape ?? false))")
                        print("isPortrait: \(orientation?.isPortrait ?? false))")
                        print("isFlat: \(orientation?.isFlat ?? false))")
                    }
            }
            .onDisappear {
                orientationChangePublisher?.cancel()
            }
    }

}

#Preview {
    OrientationView()
}

Best regards.

  • This works perfectly fine for me, thank you for sharing!

Add a Comment