iPhone X 将对象底部与安全区域对齐会破坏其他设备上的外观

iPhone X Aligning object bottom to Safe Area ruins look on other devices

关于 iPhone X Autolayout 怪癖的问题。

我有两个按钮,以前它们会与超级视图底部对齐,偏移量为 20,以免它们接触屏幕底部(我已经将 link 更改为安全区域,而不是超级视图)。

这是原始设置:

在旧版 iPhone 上看起来不错。

现在底部约束上的 20 常量现在使按钮看起来很时髦并且离 iPhone X 上的主页栏太远。

从逻辑上讲,我需要从 iPhone X 的约束中删除 20 常量,并让按钮直接与安全区域的底部对齐。

现在在 iPhone X 上看起来不错。

但现在它在非家庭酒吧电话上将按钮放置得太靠近屏幕底部。

我在 Apple 文档中遗漏了这个问题的任何直接解决方案?不能使用尺寸 classes,因为在这种情况下 iPhone X 尺寸 class 与其他 iPhones 重叠。

我可以轻松地编写代码来检测 iPhone X 并将约束上的常量设置为 0,但我希望有一个更优雅的解决方案。

谢谢,

Apple 文档指出 iOS11 中有一个新的声明可以解决这个问题。目前 iPhone X 和 iPhone 8 共享相同的大小 class 所以我们必须想出另一个解决方案。

var additionalSafeAreaInsets: UIEdgeInsets { 设置 }

在您的 AppDelegate 中添加以下代码,rootViewController 的所有子级都将继承额外的安全区域。下面的示例屏幕截图描述了此行为。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.

    if !self.isIphoneX() {
        self.window?.rootViewController?.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 20, right: 0)
    }

    return true
}

func isIphoneX() -> Bool {
    if #available(iOS 11.0, *) {
        if ((self.window?.safeAreaInsets.top)! > CGFloat(0.0)) {
            return true;
        }
    }
    return false
}

iPhone X Interface Builder 与安全区域对齐

iPhone 8 Interface Builder 对齐到安全区域

iPhone X 模拟器 - 主屏幕

iPhone X 模拟器 - 详细信息屏幕

iPhone 8 模拟器 - 主屏幕

iPhone 8 模拟器 - 详细信息屏幕

另一种直接从故事板实现这一点的方法是创建两个约束:
1. 在你的元素和安全区域之间,优先级为 250,常量为 0
2. 在你的元素和 superview 底部之间具有 750 优先级和 20 常量和 greater Than or Equal 关系。

在花了相当多的时间尝试解决这种类型的问题后,最初使用 ,我 运行 遇到了一个没有解决问题的情况 - 特别是在以下情况下"safe area" 是非零高度但不是屏幕的安全区域意味着偏移量是 0 而不是最小值 20。示例是通过 additionalSafeAreaInsets 设置底部安全区域的任意视图控制器。

解决方案是在安全区域insets变化时检查我们的视图是否与window非零安全区域对齐,并根据此调整约束的底部偏移到安全区域。以下导致矩形样式屏幕底部偏移20pt,安全区域样式屏幕全屏为0(iPhone X,最新iPad Pro,iPad slide旁白等)。

// In UIView subclass, or UIViewController using viewSafeAreaInsetsDidChange instead of safeAreaInsetsDidChange

@available(iOS 11.0, *)
override func safeAreaInsetsDidChange() {
    super.safeAreaInsetsDidChange()
    isTakingCareOfWindowSafeArea = self.isWithinNonZeroWindowBottomSafeArea
}

private var isTakingCareOfWindowSafeArea = false {
    didSet {
        guard isTakingCareOfWindowSafeArea != oldValue else { return }
        // Different offset based on whether we care about the safe area or not
        let offset: CGFloat = isTakingCareOfWindowSafeArea ? 0 : 20
        // bottomConstraint is a required bottom constraint to the safe area of the view.
        bottomConstraint.constant = -offset
    }
}
extension UIView {

    /// Allows you to check whether the view is dealing directly with the window's safe area. The reason it's the window rather than
    /// the screen is that on iPad, slide over apps and such also have this nonzero safe area. Basically anything that doesn't have a square area (such as the original iPhones with rectangular screens).
    @available(iOS 11.0, *)
    var isWithinNonZeroWindowBottomSafeArea: Bool {

        let view = self

        // Bail if we're not in a window
        guard let window = view.window else { return false }
        let windowBottomSafeAreaInset = window.safeAreaInsets.bottom

        // Bail if our window doesn't have bottom safe area insets
        guard windowBottomSafeAreaInset > 0 else { return false }

        // Bail if our bottom area doesn't match the window's bottom - something else is taking care of that
        guard windowBottomSafeAreaInset == view.safeAreaInsets.bottom else { return false }

        // Convert our bounds to the window to get our frame within the window
        let viewFrameInWindow = view.convert(view.bounds, to: window)

        // true if our bottom is aligned with the window
        // Note: Could add extra logic here, such as a leeway or something
        let isMatchingBottomFrame = viewFrameInWindow.maxY == window.frame.maxY

        return isMatchingBottomFrame

    }

}