How to Get Screen Size in Swift and SwiftUI

How to Get Screen Size in Swift and SwiftUI

Written by

in

Quick answer: In UIKit on iOS 16 and later, retrieve screen dimensions through the active window scene using view.window?.windowScene?.screen.bounds instead of the deprecated UIScreen.main. In SwiftUI, avoid manual screen calculations by using GeometryReader or .containerRelativeFrame(.horizontal) { length, axis in length * 0.75 }.

Why this happens

Historically, iOS developers relied on UIScreen.main.bounds to calculate widths and heights for interface elements. However, Apple deprecated UIScreen.main in iOS 16. On modern versions of iOS and iPadOS, an app does not always occupy the entire physical display. Features like Split View, Slide Over, Stage Manager, and external display support mean your app’s window often differs significantly from the hardware screen size.

Relying on raw screen dimensions also introduces layout bugs across device rotations. When a device rotates, physical screen dimensions do not automatically update custom views that cached width and height at launch. In modern Swift code, view sizing should be determined by window bounds, view hierarchies, or container frames rather than physical device boundaries.

How to fix it

1. Read window bounds via UIWindowScene in UIKit

If you are writing UIKit code and need the actual screen or window dimensions, read them from the active UIWindowScene attached to your view hierarchy instead of calling UIScreen.main.

Inside a UIViewController:

import UIKit

class SampleViewController: UIViewController {
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        // The view's own bounds represent the available space for your layout
        let availableWidth = view.bounds.width
        let availableHeight = view.bounds.height

        // If you specifically need the physical screen size of this window's display:
        if let screenBounds = view.window?.windowScene?.screen.bounds {
            let screenWidth = screenBounds.width
            let screenHeight = screenBounds.height
            print("Screen width: \(screenWidth), height: \(screenHeight)")
        }
    }
}

If you need screen dimensions in a standalone helper method where no UIView reference is directly available, query the connected scenes from UIApplication:

import UIKit

func currentScreenBounds() -> CGRect? {
    let scenes = UIApplication.shared.connectedScenes
    let windowScene = scenes.first { $0.activationState == .foregroundActive } as? UIWindowScene
    return windowScene?.screen.bounds
}

2. Use containerRelativeFrame or GeometryReader in SwiftUI

If you want an element to occupy 75% of the visible container in SwiftUI, do not query screen dimensions manually. For apps targeting iOS 17 and later, use .containerRelativeFrame:

import SwiftUI

struct ProportionalButtonView: View {
    var body: some View {
        Button("Continue") {
            // Action
        }
        .buttonStyle(.borderedProminent)
        .containerRelativeFrame(.horizontal) { length, axis in
            length * 0.75
        }
    }
}

For backwards compatibility with iOS 16 and earlier, wrap the layout in a GeometryReader:

import SwiftUI

struct ProportionalLayoutView: View {
    var body: some View {
        GeometryReader { geometry in
            VStack {
                Button("Continue") {
                    // Action
                }
                .frame(width: geometry.size.width * 0.75)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
    }
}

3. Use proportional Auto Layout constraints in UIKit

Calculating view frames as screenWidth * 0.75 in UIKit breaks when orientation changes or when running in iPad multitasking. Use Auto Layout with dimension multipliers instead:

import UIKit

class LayoutHelper {
    static func addProportionalButton(to parentView: UIView) -> UIButton {
        let button = UIButton(type: .system)
        button.setTitle("Submit", for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        parentView.addSubview(button)

        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: parentView.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: parentView.centerYAnchor),
            // Sets the button width to exactly 75% of its parent view
            button.widthAnchor.constraint(equalTo: parentView.widthAnchor, multiplier: 0.75),
            button.heightAnchor.constraint(equalToConstant: 50)
        ])

        return button
    }
}

How to prevent it

  • Do not use UIScreen.main.bounds for view sizing; compiler warnings flag this as deprecated in modern SDKs.
  • Prefer layout multipliers via Auto Layout constraints over manual frame math in UIKit.
  • Use view.bounds inside viewDidLayoutSubviews() if you must calculate frames manually, as bounds updates automatically when orientation changes.
  • Rely on SwiftUI container tools (GeometryReader or .containerRelativeFrame) to adapt cleanly to iPad Split View and macOS Catalyst.

FAQ

Why is UIScreen.main deprecated in iOS 16?

Apple deprecated UIScreen.main because iOS devices can support multiple connected displays and multiple concurrent windows. An app can no longer assume there is only one “main” screen.

How do I get safe area insets along with screen size?

In UIKit, inspect view.safeAreaInsets inside viewDidLayoutSubviews(). In SwiftUI, safe areas are handled automatically, but you can read exact insets using geometry.safeAreaInsets inside a GeometryReader.

Does screen size change when the user rotates the device?

Yes. The width and height values swap when transitioning between portrait and landscape modes. Reading dimensions inside lifecycle methods like viewDidLoad will yield stale data after rotation.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.