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

42

我的应用程序针对无框设备(iPhoneX,Xs Xs max,Xr)有一些逻辑。目前它基于设备的型号工作,所以我通过DeviceKit框架检测型号。

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

我们无法依赖FaceID、safeAreaInset、屏幕高度或大小。那么,怎么办呢?


6
不要根据屏幕大小在你的应用程序中做决定。这种方法不可扩展且难以维护。 - Cristik
1
@Cristik,是的,我知道。 - Mark cubn
1
@ slickdaddy,这个应用程序是为客户而设计的,他们不想推送太多更新,我希望能节省他们的时间。 - Mark cubn
请参考以下内容的翻译:https://dev59.com/h1YO5IYBdhLWcg3wMO1y#46192822 - Anbu.Karthik
@Cristik 你的意思是什么?每个移动端断点样式都是基于屏幕尺寸在你的应用程序中做出的“决策”。 - duhaime
显示剩余2条评论
18个回答

65
你可以“筛选”出顶级的东西,就像这样:
var hasTopNotch: Bool {
    if #available(iOS 11.0, tvOS 11.0, *) {
        return UIApplication.shared.delegate?.window??.safeAreaInsets.top ?? 0 > 20
    }
    return false
}

8
在 iPad Pro 上,statusBarHeight 的值为 24pt,这个方法不适用。 - MarekR
1
MarekR -> 尝试使用".bottom"而不是".top" - Haseeb Javed
"fitler" ?你是不是想说 "filter"? - StackUnderflow

26

支持Swift 5和iOS 14

感谢@Tanin和@DominicMDev,由于且iPad Pro存在非零safeAreaInsets,这对我来说可以正常工作。

(已在、和iPad Pro (11英寸) (第2代)模拟器上测试通过)

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)

@NikhilMuskur 我刚刚在iOS 14.1,Xcode 12.1上尝试了iPhone 8 Plus、iPhone 12 Pro和iPad Pro(11英寸),它们都可以正常工作。你的情况如何? - Saafo
1
在模拟器中一切都正常,但是当我在设备上运行时,“isPortrait”属性总是为false。 - Nikhil Muskur
在调试时,我发现 UIDevice.current.orientation.isPortrait 始终返回 false,而 window.safeAreaInsets.left 等于 0。我在 iPhone 11 上测试了这个问题。奇怪的是,模拟器上该条件却正确运行。 - Nikhil Muskur
2
可以确认,不起作用,对于UIWindow的rootViewController,后来它起作用了。 - David
如果手机具有动态岛屿,这也将是“true”。 - Micro
显示剩余2条评论

11

自从在iOS 13中keyWindow已被弃用,根据从这里找到的解决方案,这个方法对我有用

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
这段代码在第三代11英寸的iPad上会出现问题,因为该设备的 keyWindow?.safeAreaInsets.bottom == 20 ,由于它没有底部按钮。最终我使用了"Saafo"的答案。 - Vladimir Amiorkov

7

这适用于任何方向。 无需担心iOS版本在11.0之前的问题,因为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
keyWindow现已被弃用。 - claude31

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

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

使用者

if UIDevice.current.hasNotch 
{
    //... consider notch
} 
else 
{
   //... don't have to consider notch
}

2
我这样做是因为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
        }
    }

}

1

由于设备方向与界面方向不同,因此我的最终代码如下:

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

1

对于使用场景代理的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
  }
}

1
根据提供的所有答案,我需要另一种方法。我创建了一个包含所有必要接口细节的枚举,并将所需类型返回到一个变量中:
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:
//
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接