Swift
orientation changes
iOS development
mobile app development
programming tutorial

Swift - How to detect orientation changes

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Detecting when a user rotates their device is essential for building responsive iOS apps. Whether you need to rearrange a layout, resize a video player, or switch between grid and list views, your app must respond to orientation changes smoothly. This article covers every major technique in UIKit and SwiftUI, explains the critical difference between device and interface orientation, and helps you choose the right approach for your situation.

UIDevice.orientationDidChangeNotification

The most straightforward way to detect orientation changes in UIKit is to observe the UIDevice.orientationDidChangeNotification notification. This fires whenever the physical device rotates, regardless of whether the interface actually changes.

swift
1import UIKit
2
3class MyViewController: UIViewController {
4    override func viewDidLoad() {
5        super.viewDidLoad()
6
7        // Start generating orientation notifications
8        UIDevice.current.beginGeneratingDeviceOrientationNotifications()
9
10        NotificationCenter.default.addObserver(
11            self,
12            selector: #selector(orientationDidChange),
13            name: UIDevice.orientationDidChangeNotification,
14            object: nil
15        )
16    }
17
18    @objc func orientationDidChange() {
19        let orientation = UIDevice.current.orientation
20        switch orientation {
21        case .portrait:
22            print("Portrait")
23        case .landscapeLeft:
24            print("Landscape Left")
25        case .landscapeRight:
26            print("Landscape Right")
27        case .portraitUpsideDown:
28            print("Portrait Upside Down")
29        case .faceUp, .faceDown:
30            print("Flat on surface")
31        default:
32            print("Unknown")
33        }
34    }
35
36    deinit {
37        UIDevice.current.endGeneratingDeviceOrientationNotifications()
38    }
39}

Remember to call beginGeneratingDeviceOrientationNotifications() or the notification will never fire. Also note that faceUp and faceDown are device-only orientations that do not correspond to any interface orientation.

UIDeviceOrientation vs UIInterfaceOrientation

Understanding the difference between these two types is critical because confusing them is one of the most common mistakes in iOS orientation handling.

UIDeviceOrientation describes how the physical device is held. It includes six cases: portrait, portraitUpsideDown, landscapeLeft, landscapeRight, faceUp, and faceDown. It comes from the accelerometer.

UIInterfaceOrientation describes how the app's UI is displayed. It only includes four cases: portrait, portraitUpsideDown, landscapeLeft, and landscapeRight. It comes from the window scene.

swift
1// Device orientation (from hardware sensors)
2let deviceOrientation = UIDevice.current.orientation
3
4// Interface orientation (from the window scene)
5if let windowScene = view.window?.windowScene {
6    let interfaceOrientation = windowScene.interfaceOrientation
7}

A key subtlety is that landscapeLeft means opposite things for each type. UIDeviceOrientation.landscapeLeft means the device is rotated so the home button is on the right, while UIInterfaceOrientation.landscapeLeft means the interface is rotated to the left. Use interface orientation when you need to know how your UI is laid out.

viewWillTransition(to:with:)

For UIKit view controllers, overriding viewWillTransition(to:with:) is the recommended way to react to size changes caused by rotation. This method gives you the new size before the transition animation begins.

swift
1override func viewWillTransition(
2    to size: CGSize,
3    with coordinator: UIViewControllerTransitionCoordinator
4) {
5    super.viewWillTransition(to: size, with: coordinator)
6
7    let isLandscape = size.width > size.height
8    print("Transitioning to \(isLandscape ? "landscape" : "portrait")")
9
10    // Animate alongside the rotation
11    coordinator.animate(alongsideTransition: { context in
12        // Update layout here
13        self.updateLayout(for: size)
14    }, completion: { context in
15        // Transition finished
16        print("Rotation complete")
17    })
18}

This method is preferred over notification-based approaches when you need to coordinate your own animations with the system rotation animation.

traitCollectionDidChange for Size Classes

Size classes provide a more abstract way to handle layout changes. Instead of checking for specific orientations, you respond to changes between compact and regular size classes.

swift
1override func traitCollectionDidChange(
2    _ previousTraitCollection: UITraitCollection?
3) {
4    super.traitCollectionDidChange(previousTraitCollection)
5
6    if traitCollection.horizontalSizeClass != previousTraitCollection?.horizontalSizeClass {
7        switch traitCollection.horizontalSizeClass {
8        case .compact:
9            print("Compact width - likely portrait on iPhone")
10        case .regular:
11            print("Regular width - likely landscape or iPad")
12        default:
13            break
14        }
15    }
16}

Size classes are the right choice when you want your layout logic to work across iPhones, iPads, and multitasking split views without hard-coding device-specific orientation checks.

SwiftUI Orientation Detection

In SwiftUI, you can read the horizontal size class from the environment. This is the SwiftUI equivalent of trait collection changes.

swift
1import SwiftUI
2
3struct ContentView: View {
4    @Environment(\.horizontalSizeClass) var horizontalSizeClass
5    @Environment(\.verticalSizeClass) var verticalSizeClass
6
7    var body: some View {
8        Group {
9            if horizontalSizeClass == .compact {
10                VStack {
11                    Text("Portrait Layout")
12                    // Stack items vertically
13                }
14            } else {
15                HStack {
16                    Text("Landscape Layout")
17                    // Arrange items horizontally
18                }
19            }
20        }
21    }
22}

For cases where you need the actual device orientation in SwiftUI, you can combine a notification observer with a state variable.

swift
1struct OrientationView: View {
2    @State private var orientation = UIDevice.current.orientation
3
4    var body: some View {
5        Text("Orientation: \(orientation.isLandscape ? "Landscape" : "Portrait")")
6            .onReceive(
7                NotificationCenter.default.publisher(
8                    for: UIDevice.orientationDidChangeNotification
9                )
10            ) { _ in
11                orientation = UIDevice.current.orientation
12            }
13    }
14}

Common Pitfalls

  • Forgetting beginGeneratingDeviceOrientationNotifications() means UIDevice.orientationDidChangeNotification never fires, and your handler is silently ignored.
  • Using UIDeviceOrientation to determine interface layout includes faceUp and faceDown which have no corresponding interface orientation, leading to unexpected behavior.
  • Not using the transition coordinator in viewWillTransition causes your custom layout changes to appear out of sync with the system rotation animation.
  • Hard-coding orientation checks instead of size classes breaks on iPads in split view, where the device may be in landscape but your app has a compact width.
  • Checking orientation in viewDidLoad may return .unknown because the device orientation may not be determined yet at that point in the lifecycle.

Summary

  • Use UIDevice.orientationDidChangeNotification with NotificationCenter for direct device rotation detection in UIKit.
  • Override viewWillTransition(to:with:) to animate layout changes alongside the system rotation transition.
  • Understand that UIDeviceOrientation tracks the physical device, while UIInterfaceOrientation tracks the app's UI. These are not interchangeable.
  • Use traitCollectionDidChange and size classes for adaptive layouts that work across all device types and multitasking modes.
  • In SwiftUI, use @Environment(\.horizontalSizeClass) for declarative layout switching based on available space.
  • Always prefer size classes over explicit orientation checks for forward-compatible, multitasking-ready code.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.