Help with changing the height of a UIView based on the device

I am trying to change the height of a UIView based on which device the user is on. For example, I have created a view with an alpha of 25%. This view is pinned to the top, left, and right of the screen. I need the view to have a height of 44 when viewed on devices that have the cutout at the top (i.e. iPhone 11...etc.) and a height of 22 when viewed on devices that do not have the cutout (i.e. iPhone 8s Plus..etc.).


I am trying to accomplish this by using Vary for Traits in Xcode and not successful (I am sure it's because that is the wrong way to do it. LOL).

Please let me know if you need additional information or have any questions. I appreciate your time and help in advance.


Thank you,

mz

Accepted Reply

I don't understand either.

Most likeley, UIApplication.shared.delegate?.window. is nil

Could you check if UIApplication.shared.delegate is nil or not ?


Does the AppDelegate class contains this :


@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


Are you running on simulator or device ?


I tested with 13.2 target, still works.


I may have found the difference.

Tested on a project with SceneDelegate.


And then, notch is false, even on X11 Pro.


So, I replaced by this extension:

extension UIDevice {
    var hasNotch: Bool {
        if #available(iOS 11.0, *) {
            if UIApplication.shared.windows.count == 0 { return false }          // Should never occur, but…
            let top = UIApplication.shared.windows[0].safeAreaInsets.top
            return top > 20          // That seem to be the minimum top when no notch…
        } else {
            // Fallback on earlier versions
            return false
        }
    }
}

Credit:

h ttps://www.reddit.com/r/swift/comments/cap2r4/how_to_access_uiwindow_from_scenedelegate_in_ios/


That seems to work.

Replies

An idea on how to do it.


1. Detect if there is a notch (cutout)


extension UIDevice {
    var hasNotch: Bool {
        if #available(iOS 11.0, *) {
            let bottom = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
            return bottom > 0
        } else {
            // Fallback on earlier versions
            return false
        }
    }
}



2. In the ViewController viewDidLoad, adapt the height of the view

// Declare the IBOutlet

    @IBOutlet weak var viewAtNotch: UIView!

// set constraints to the top in IB


// in viewDidLoad

        if UIDevice.current.hasNotch {
            viewAtNotch.frame.size.height = 44
        } else {
            viewAtNotch.frame.size.height = 22
        }

Hi Claude31,


I appreciate the quick response and information. I tried the example above and the detection always fallsback to no notch. I am using the simulator and the same return happens for iPhone X or an iPhone 8 (no notch).


Any suggestions?


Thank you,

mz

I tested this code in XCode 11.2ß2 and iOS 13.2 simulator

print("notch", UIDevice.current.hasNotch)


iPhone XS and XS Max => returns true

iPhone 8 (no notch). => returns false

Seen this (lengthy/bit dated?) SO thread? Good info on working w/the notch, especially if you're stuck simulator testing only...


https://stackoverflow.com/questions/46192280/detect-if-the-device-is-iphone-x/47067296

Hi Claude31,


Yes, each time I run a different device (notch or no notch) my print message says "without notch". In addition, if I set top, leading, trailing, and height (gives errors if I don't set height) constraints on the UIView, then the height will not change to what I have set in the code.


I am testing code in Xcode 11.3(11C29) and iOS 13.3 simulator.


Thank you,

mz

I tested in Xcode 11.3 (11C29) and iOS 13.3 simulator for iPhone 11 ProMax - 13.3. OSX 10.14.6


I get true for notch.

No doubt it's something I am doing wrong...lol. Below is my code.


ViewController.swif

import UIKit

class ViewController: UIViewController {
   
    @IBOutlet weak var viewAtNotch: UIView!
//    var viewAtNotch = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
       
        if UIDevice.current.hasNotch {
            viewAtNotch.frame.size.height = 44
            print("has notch")
        } else {
            viewAtNotch.frame.size.height = 22
            print("without notch")
        }
    }
}


UIDevice+Notch.swif

import Foundation
import UIKit

extension UIDevice {
    var hasNotch: Bool {
        let bottom = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
        print(bottom)
        return bottom > 0
    }
}


And no matter which device I simulate (from the biggest iPad to the smallest iPhone) it returns "without notch".


mz

What is the result of

        print(bottom)


And complementing the print:

print("bottom", bottom, "keyWindow", UIApplication.shared.keyWindow)

The result of print(bottom) is 0.0.


I updated print per you suggestion to include 'keyWindow'.


print("bottom", bottom, "keyWindow", UIApplication.shared.keyWindow)


result:

bottom 0.0 keyWindow nil


Also, I did not add this before, but 'keyWindow' was deprecated in iOS 13.0.

'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes


Thank you,

mz

I did not notice the deprecation.


Could try this, credit to. https://stackoverflow.com/questions/52402477/ios-detect-if-the-device-is-iphone-x-family-frameless


extension UIDevice {
    var hasNotch: Bool {
        if #available(iOS 11.0, *) {
            let top = UIApplication.shared.delegate?.window??.safeAreaInsets.top ?? 0
            return top > 0
        } else {
            // Fallback on earlier versions
            return false
        }
    }
}



Note that the double optional chaining ??. is not an error: https://stackoverflow.com/questions/28901893/why-is-main-window-of-type-double-optional

Still no go. Not sure what I am doing for it not to work. 😐 Below is the updated code.


UIDevice+Notch.swif

import Foundation
import UIKit

extension UIDevice {
    var hasNotch: Bool {
//        return UIApplication.shared.delegate?.window??.safeAreaInsets.bottom ?? 0 > 0
        let top = UIApplication.shared.delegate?.window??.safeAreaInsets.top ?? 0
        print("top" top)
        return top > 0
    }
}


ViewController.swif

import UIKit



class ViewController: UIViewController {

    @IBOutlet weak var viewAtNotch: UIView!
    @IBOutlet weak var statusBarBackgroundHeight: NSLayoutConstraint!
//    var viewAtNotch = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        if UIDevice.current.hasNotch {
            self.statusBarBackgroundHeight.constant = 44
//            viewAtNotch.frame.size.height = 44
            print("has notch")
        } else {
            self.statusBarBackgroundHeight.constant = 22
//            viewAtNotch.frame.size.height = 22
            print("without notch")
        }
    }
}



Results for iPhone 11 Pro Max running 13.3 are:

top 0.0
without notch

I don't understand either.

Most likeley, UIApplication.shared.delegate?.window. is nil

Could you check if UIApplication.shared.delegate is nil or not ?


Does the AppDelegate class contains this :


@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


Are you running on simulator or device ?


I tested with 13.2 target, still works.


I may have found the difference.

Tested on a project with SceneDelegate.


And then, notch is false, even on X11 Pro.


So, I replaced by this extension:

extension UIDevice {
    var hasNotch: Bool {
        if #available(iOS 11.0, *) {
            if UIApplication.shared.windows.count == 0 { return false }          // Should never occur, but…
            let top = UIApplication.shared.windows[0].safeAreaInsets.top
            return top > 20          // That seem to be the minimum top when no notch…
        } else {
            // Fallback on earlier versions
            return false
        }
    }
}

Credit:

h ttps://www.reddit.com/r/swift/comments/cap2r4/how_to_access_uiwindow_from_scenedelegate_in_ios/


That seems to work.

The updated extension code seems to do the trick. I did update return top > 20 to return top > 24. This will include the iPad.


credit MarekR - https://stackoverflow.com/questions/52402477/ios-detect-if-the-device-is-iphone-x-family-frameless


Thanks for the help! Happy Holidays!


mz

Claude31,


I have one additional and final question. I want the bar to show behind the status bar in portrait mode, but not in landscape mode for all iPhones. I want to show the bar behind the status bar in portrait and landscape for iPads. The code below (for the most part) works for iPhones that do not have a notch and iPads. iPhones with a notch work fine in portrait and landscape, except when you go back to portrait the bar's height is set to 22 because it does not detect the notch. I have to open another view and go back for the extension code to be read again. I have spent time trying different things and searching. Thoughts/suggestions?


UIDevice+Notch.swift

import Foundation
import UIKit

extension UIDevice {
    var hasNotch: Bool {
        if UIApplication.shared.windows.count == 0 { return false }
        let top = UIApplication.shared.windows[0].safeAreaInsets.top
        print("top", top)
        print("windows", UIApplication.shared.windows)
        return top > 24
    }
}


ViewController.swift

import UIKit

class ViewController: UIViewController {
   
    @IBOutlet weak var viewAtNotch: UIView!
    @IBOutlet weak var statusBarBackgroundHeight: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
       
        if UIDevice.current.hasNotch {
            self.statusBarBackgroundHeight.constant = 44
            print("has notch")
        } else {
            self.statusBarBackgroundHeight.constant = 22
            print("without notch")
        }
    }
   
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
       
        if UIDevice.current.orientation.isLandscape {
            print("Landscape")
            if UIDevice.current.hasNotch {
                self.statusBarBackgroundHeight.constant = 0
                print("Device is in Landscape - has a notch")
            } else {
                if UIDevice.current.userInterfaceIdiom == .pad {
                    self.statusBarBackgroundHeight.constant = 22
                    print("Device is in Landscape and is an iPad - no notch")
                } else {
                    self.statusBarBackgroundHeight.constant = 0
                    print("Device is in Landscape and not an iPad - no notch")
                }
            }
        } else {
            print("Portrait")
            if UIDevice.current.hasNotch {
                self.statusBarBackgroundHeight.constant = 44
                print("Device is in Portrait - has a notch")
            } else {
                self.statusBarBackgroundHeight.constant = 22
                print("Device is in Portrait - no notch")
            }
        }
    }
}

You should refine the extension to test here for Portrait or Landscape and adapt.


Something inspired from the SO post: https://stackoverflow.com/questions/52402477/ios-detect-if-the-device-is-iphone-x-family-frameless


var hasTopNotch: Bool 
{
  if #available(iOS 11.0, *) {

  var safeAreaInset: CGFloat?
  if (UIApplication.shared.statusBarOrientation == .portrait) {
  safeAreaInset = UIApplication.shared.delegate?.window??.safeAreaInsets.top
  }
  else if (UIApplication.shared.statusBarOrientation == .landscapeLeft) {
  safeAreaInset = UIApplication.shared.delegate?.window??.safeAreaInsets.left
  }
  else if (UIApplication.shared.statusBarOrientation == .landscapeRight) {
  safeAreaInset = UIApplication.shared.delegate?.window??.safeAreaInsets.right
  }
  return safeAreaInset ?? 0 > 24
  }
  return false
}