iOS:检测设备是否为 iPhone X 系列(无框)

iOS: Detect if the device is iPhone X family (frameless)

提问人:Mark cubn 提问时间:9/19/2018 更新时间:11/6/2023 访问量:32205

问:

在我的应用程序中,有一些无框设备(iPhoneX、Xs、Xs、max、Xr)的逻辑。目前,它基于设备的模型工作,因此,我通过DeviceKit框架检测模型。

但我想将这种逻辑扩展到未来的无框设备。可能在一年内,我们将拥有一些额外的无框设备。那么,如何检测设备是否为无框?它应该涵盖所有当前的无框设备和未来的无框设备。

我们不能依赖 faceID、safeAreaInset、屏幕高度或大小。那么,然后呢?

iOS的 iPhone Swift UI查看 iOS11

评论

6赞 Cristik 9/19/2018
不要根据屏幕尺寸在应用中做出决定。它根本无法扩展且难以维护。
1赞 Mark cubn 9/19/2018
@Cristik,是的,我知道
0赞 trndjc 9/19/2018
Apple 没有生产很多运行 iOS 的不同型号,因此只需在设备发布时更新设备列表即可。我想这将是每年大约 10 分钟的工作。
1赞 Mark cubn 9/19/2018
@slickdaddy,该应用程序是为客户准备的,他们不想推送大量更新,我想节省他们的时间
0赞 Anbu.Karthik 9/26/2018
例如,请参阅以下内容:stackoverflow.com/questions/46192280/...

答:

66赞 Bence Pattogato 9/19/2018 #1

您可以“过滤”出一流的内容,例如:

var hasTopNotch: Bool {
    if #available(iOS 11.0, tvOS 11.0, *) {
        return UIApplication.shared.delegate?.window??.safeAreaInsets.top ?? 0 > 20
    }
    return false
}

评论

8赞 MarekR 6/23/2019
这不适用于 statusBarHeight 为 24pt 的 iPad Pro
1赞 Haseeb Javed 2/25/2021
MarekR ->尝试“.bottom 而不是 .top”
0赞 StackUnderflow 7/4/2022
“钳工”???你的意思是“过滤器”吗?
0赞 Ahmad Bazian 5/1/2019 #2

这样,您可以涵盖所有方向:

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
}
7赞 beatrizanso 5/30/2019 #3

这适用于任何方向。 无需担心 11.0 之前的 iOS 版本,因为 iPhone X 最低版本为 11.0。

extension UIDevice {

    var hasNotch: Bool {
        if #available(iOS 11.0, *) {
           return UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0 > 0
        }
        return false
   }
}

评论

1赞 claude31 12/19/2019
keyWindow 现已弃用。
2赞 DominicMDev 12/5/2019 #4

我这样做是因为 iPadPro 具有非零 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
        }
    }

}
3赞 Azharhussain Shaikh 7/8/2020 #5
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
        }
    }
}

使用方式

if UIDevice.current.hasNotch 
{
    //... consider notch
} 
else 
{
   //... don't have to consider notch
}
3赞 Jacob Cavin 8/12/2020 #6

斯威夫特 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
    }
}
11赞 Tanin 9/2/2020 #7

由于,基于从这里找到的解决方案,这个对我有用keyWindow was deprecated in iOS 13keyWindow

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
    }

}

评论

1赞 Vladimir Amiorkov 2/1/2022
这在 iPad 11 英寸 3 代上中断,因为该设备有一个,因为它没有底部按钮。“Saafo”的答案是我最后使用的答案。keyWindow?.safeAreaInsets.bottom == 20
26赞 Saafo 9/18/2020 #8

支持 Swift 5、iOS 14

感谢 @Tanin 和 @DominicMDev,因为 和 ,这对我来说很好用。keyWindow was deprecated in iOS 13the iPad Pro has non-zero safeAreaInsets

(已在 和 模拟器上测试)iPhone 8iPhone 11 ProiPad Pro (11-inch)(2nd gen)

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
        }
    }
}

使用示例:

print(UIDevice.current.hasNotch)

评论

0赞 Saafo 11/11/2020
@NikhilMuskur 我刚才在 iOS 8、Xcode 12 上的 iPhone 12 Plus、iPhone 11 Pro 和 iPad Pro(14.1 英寸)上尝试过,效果很好。你是什么情况?
1赞 Nikhil Muskur 11/12/2020
在模拟器中一切正常,但是当我在设备上运行它时,该属性始终为falseisPortrait
0赞 Saafo 11/12/2020
您@NikhilMuskur能更准确地描述您的情况吗?在什么设备上以及如何指定属性始终为 false?isPortrait
0赞 Nikhil Muskur 11/12/2020
在调试时,我发现它总是返回 false 并且等于 0。我在 iPhone 11 上测试了这一点。奇怪的是,该条件在模拟器上正常工作UIDevice.current.orientation.isPortraitwindow.safeAreaInsets.left
2赞 David 11/28/2020
可以确认,不工作,对于 UIVindow 的 rootViewController,稍后 - 它可以工作
-1赞 CS Prasad 1/22/2021 #9
extension UIDevice {
    var hasNotch: Bool {
        let bottom = UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0
        return bottom > 0
    }
}

使用 'UIDevice.current.hasNotch' 将返回 true 或 false

斯威夫特 5

var hasNotch: Bool {
    let bottom = UIApplication.shared.delegate?.window??.safeAreaInsets.bottom ?? 0
    return bottom > 0
}

评论

0赞 zhisme 1/22/2021
倡导你的代码是一个很好的做法,考虑在你的答案中添加一些描述
1赞 fishinear 4/24/2021
还要描述您的答案与现有 8 个答案有何不同。
0赞 J A S K I E R 1/14/2022 #10
safeTop = UIApplication.shared.delegate?.window??.safeAreaInsets.top ?? 0

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

斯威夫特 5.0最容易理解和适应您的条件。

评论

0赞 Joseph 12/13/2022
这对我不起作用——使用 iPhone 14 时打印了“Before X”。
1赞 z33 1/19/2022 #11

由于⚠️设备方向 != 接口方向⚠️

我的最终代码是:

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
        }
    }
}
0赞 idris yıldız 1/31/2022 #12
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
        }
    }

评论

0赞 idris yıldız 1/31/2022
编号: developer.apple.com/forums/thread/127113?page=2
0赞 Ariven Nadar 4/11/2022 #13
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
}
0赞 Vadim Bashurov 4/20/2022 #14

如果要检查 UIViewController 中的缺口,只需在方法 viewSafeAreaInsetsDidChange() 中设置 isNotch 标志

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

我只在模拟器上测试过它。它仅在所有方向的 X 设备中被调用。

0赞 Muhammad Ahmad 7/21/2022 #15

当你有

场景委托.swift

类。这将起作用

 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
            }
        }
    }
1赞 Tulleb 2/9/2023 #16

对于使用场景委托的 SwiftUI:

  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
  }
}
0赞 McCaffers 6/24/2023 #17

我已经建立在 Saafo 的答案之上,现在在 iOS 15.0 中已弃用,如果方向未知,我也默认检查顶部插入UIApplication.shared.windows

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
        }
    }
}

1赞 PollyVern 11/6/2023 #18

根据提供的所有答案,我需要另一种方式。我创建了一个具有所有必要接口细微差别的枚举,并在变量中返回所需的类型:

enum DeviceInterfaceType {
    case dynamicIsland
    case notch
    case none
}

extension UIDevice {

    var interfaceType: DeviceInterfaceType {
        guard let window = (UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.flatMap { $0.windows }.first { $0.isKeyWindow}) else {
        return .none
        }

        if window.safeAreaInsets.top >= 51 && userInterfaceIdiom == .phone {
            return .dynamicIsland
        } else if window.safeAreaInsets.top >= 44 {
            return .notch
        } else if window.safeAreaInsets.left > 0 || window.safeAreaInsets.right > 0 {
            return .none
        }

        return .none
    }

}

函数调用

switch UIDevice.current.interfaceType {
case .dynamicIsland:
//
case .notch:
//
case .none:
//
}