42

In my app there is some logic for frameless devices (iPhoneX, Xs Xs max, Xr). Currently it works base on the model of the devices, so, I detect the model by DeviceKit framework.

But I want to extend this logic to future frameless devices. Probably in one year we will have some extra frameless devices. So, how can I detect if device is frameless or not? It should cover all current frameless devices and future one.

We can not rely on faceID, safeAreaInset, screen height or size. So, then what?

Mark cubn
  • 507
  • 1
  • 4
  • 8

17 Answers17

65

You could "fitler" for the top notch, something like:

var hasTopNotch: Bool {
    if #available(iOS 11.0, tvOS 11.0, *) {
        return UIApplication.shared.delegate?.window??.safeAreaInsets.top ?? 0 > 20
    }
    return false
}
Bence Pattogato
  • 3,752
  • 22
  • 30
26

Swift 5, iOS 14 supported

Thanks to @Tanin and @DominicMDev, since keyWindow was deprecated in iOS 13 and the iPad Pro has non-zero safeAreaInsets, this works fine for me.

(Already tested on iPhone 8, iPhone 11 Pro and iPad Pro (11-inch)(2nd gen) Simulators)

extension UIDevice {
    /// Returns `true` if the device has a notch
    var hasNotch: Bool {
        guard #available(iOS 11.0, *), let window = UIApplication.shared.windows.filter({$0.isKeyWindow}).first else { return false }
        if UIDevice.current.orientation.isPortrait {
            return window.safeAreaInsets.top >= 44
        } else {
            return window.safeAreaInsets.left > 0 || window.safeAreaInsets.right > 0
        }
    }
}

Example of usage:

print(UIDevice.current.hasNotch)
stackich
  • 3,607
  • 3
  • 17
  • 41
Saafo
  • 459
  • 6
  • 6
  • @NikhilMuskur I've tried on iPhone 8 Plus, iPhone 12 Pro and iPad Pro (11-inch) on iOS 14.1, Xcode 12.1 just now and it works well. What's your condition? – Saafo Nov 11 '20 at 13:23
  • 1
    Everything works correctly in the simulator but when I run this on a device the `isPortrait` property is always false – Nikhil Muskur Nov 12 '20 at 08:22
  • @NikhilMuskur can you give a more precise description about your situation? On what device and how do you specified the `isPortrait` property is always false? – Saafo Nov 12 '20 at 09:35
  • While debugging I found that `UIDevice.current.orientation.isPortrait` always returned false and `window.safeAreaInsets.left` was equal to 0. I tested this on iPhone 11. Strangely the condition is working correctly on the simulator – Nikhil Muskur Nov 12 '20 at 11:53
  • @NikhilMuskur Well I've tested on my iPhone X and `UIDevice.current.orientation.isPortrait` works well ( return true when portrait )... – Saafo Nov 17 '20 at 05:56
  • 2
    Can confirm, not working, for the rootViewController of UIVindow, later on - it works – David Nov 27 '20 at 19:42
  • This will also be `true` if the phone has the dynamic island. – Micro Oct 11 '22 at 23:16
11

Since keyWindow was deprecated in iOS 13, based on the solution to find keyWindow from here, this one works for me

extension UIDevice {
    var hasNotch: Bool {
        if #available(iOS 11.0, *) {
            let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
            return keyWindow?.safeAreaInsets.bottom ?? 0 > 0
        }
        return false
    }

}
Tanin
  • 585
  • 7
  • 10
  • 1
    This breaks on iPad 11 inch 3 generation, because that device has a `keyWindow?.safeAreaInsets.bottom == 20` because it does not have a bottom button. The answer from "Saafo" is what I used in the end. – Vladimir Amiorkov Feb 01 '22 at 12:37
7

This is valid for any orientation. No need to worry about iOS version before 11.0 since iPhone X minimum version is 11.0. Source

extension UIDevice {

    var hasNotch: Bool {
        if #available(iOS 11.0, *) {
           return UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0 > 0
        }
        return false
   }
}
beatrizanso
  • 351
  • 5
  • 7
3
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
        }
    }
}

Using by

if UIDevice.current.hasNotch 
{
    //... consider notch
} 
else 
{
   //... don't have to consider notch
}
Azharhussain Shaikh
  • 1,654
  • 14
  • 17
3

Swift 5

var hasNotch: Bool {
    if #available(iOS 11.0, tvOS 11.0, *) {
        let bottom = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
        return bottom > 0
    } else {
        return false
    }
}
Jacob Cavin
  • 2,169
  • 3
  • 19
  • 47
2

I am doing it like this because the iPadPro has non-zero safeAreaInsets.

extension UIDevice {

    /// Returns 'true' if the device has a notch
    var hasNotch: Bool {
        guard #available(iOS 11.0, *), let window = UIApplication.shared.keyWindow else { return false }
        let orientation = UIApplication.shared.statusBarOrientation
        if orientation.isPortrait {
            return window.safeAreaInsets.top >= 44
        } else {
            return window.safeAreaInsets.left > 0 || window.safeAreaInsets.right > 0
        }
    }

}
DominicMDev
  • 89
  • 1
  • 1
1

Since ⚠️Device Orientation != Interface Orientation⚠️

My final code is:

extension UIDevice {
    var hasNotch: Bool {
        guard #available(iOS 11.0, *), let window = UIApplication.shared.windows.filter({$0.isKeyWindow}).first else { return false }
        //if UIDevice.current.orientation.isPortrait {  //Device Orientation != Interface Orientation
        if let o = windowInterfaceOrientation?.isPortrait, o == true {
            return window.safeAreaInsets.top >= 44
        } else {
            return window.safeAreaInsets.left > 0 || window.safeAreaInsets.right > 0
        }
    }
    
    private var windowInterfaceOrientation: UIInterfaceOrientation? {
        if #available(iOS 13.0, *) {
            return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
        } else {
            return UIApplication.shared.statusBarOrientation
        }
    }
}
z33
  • 1,193
  • 13
  • 24
1

For SwiftUI using scene delegate:

  var hasNotch: Bool {
    guard let scene = UIApplication.shared.connectedScenes.first,
          let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate,
          let window = windowSceneDelegate.window else {
      return false
    }

    return window?.safeAreaInsets.top ?? 0 > 20
  }
}
Tulleb
  • 8,919
  • 8
  • 27
  • 55
0

This way you can cover all the orientations:

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
}
0
safeTop = UIApplication.shared.delegate?.window??.safeAreaInsets.top ?? 0

if safeTop > 20 { 
   print("Big family")
} else {
   print("Before X")
}

Swift 5.0 The most simple to understand and adapt to your conditions.

J A S K I E R
  • 1,976
  • 3
  • 24
  • 42
0
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
        }
    }
idris yıldız
  • 2,097
  • 20
  • 22
0
func isIphoneX() -> Bool {
    guard #available(iOS 11.0, *),
      let window = UIApplication.shared.windows.filter({$0.isKeyWindow}).first else { return false }

    return window.safeAreaInsets.top >= 44
}
Yogurt
  • 2,913
  • 2
  • 32
  • 63
Ariven Nadar
  • 1,278
  • 13
  • 13
0

If you want to check the notch in your UIViewController just set up isNotch flag in the method viewSafeAreaInsetsDidChange()

open override func viewSafeAreaInsetsDidChange() {
    super.viewSafeAreaInsetsDidChange()
    isNotch = true
}

I've tested it on Simulator only. It is called only in X devices in all orientations.

0

when you have

sceneDelegate.swift

class. This Will Work

 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 seems to be the minimum top when no notch…
            } else {
                // Fallback on earlier versions
                return false
            }
        }
    }
Muhammad Ahmad
  • 252
  • 3
  • 13
0

I've built upon Saafo's answer as UIApplication.shared.windows is now deprecated in iOS 15.0 and I've also defaulted to checking the top insert if the orientation is unknown

xcode warning on deprecated function

extension UIDevice {

    var hasNotch: Bool {
        
        guard let firstScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
            return false
        }

        guard let firstWindow = firstScene.windows.filter({$0.isKeyWindow}).first else {
            return false
        }
        
        if UIDevice.current.orientation.isPortrait ||
            UIDevice.current.orientation == .unknown {
            return firstWindow.safeAreaInsets.top >= 44
        } else {
            return firstWindow.safeAreaInsets.left > 0 || firstWindow.safeAreaInsets.right > 0
        }
    }
}

McCaffers
  • 46
  • 3
-1
extension UIDevice {
    var hasNotch: Bool {
        let bottom = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
        return bottom > 0
    }
}

use 'UIDevice.current.hasNotch' will return true or false

Swift 5

var hasNotch: Bool {
    let bottom = UIApplication.shared.delegate?.window??.safeAreaInsets.bottom ?? 0
    return bottom > 0
}
CS Prasad
  • 1
  • 3