IPhone X Выравнивание дна объекта в руинах Safe Area на других устройствах

Вопрос об iPhone X Autolayout quirks.

У меня есть две кнопки, ранее они были бы выровнены снизу до супервизора со смещением 20, чтобы они не касались нижней части экрана (с тех пор я изменил ссылку на безопасную зону, а не на просмотр).

Здесь оригинальная настройка: iPhone 8 - Setup

Выглядит хорошо, как ожидалось, на более старых iPhone. iPhone 8 20 Offset

Теперь 20 констант в нижнем ограничении теперь заставляют кнопки выглядеть фанки и слишком далеко от домашней панели на iPhone X. iPhone X 20 Offset

Логически, мне нужно удалить константу 20 из ограничения на iPhone X и выровнять кнопки непосредственно со дном безопасной области. iPhone X - Setup

Выглядит хорошо на iPhone X сейчас. iPhone X 0 Offset

Но теперь он помещает кнопки слишком близко к нижней части экрана на телефоны, не принадлежащие домашним барам. iPhone 8 0 Offset

Какие-либо немедленные решения этой проблемы, которые я пропустил в документах Apple? Невозможно использовать классы размера, так как в этом случае класс размера iPhone X накладывается на другие iPhone.

Я мог легко закодировать для обнаружения iPhone X и установить константу ограничения 0, но я надеялся на более элегантное решение.

Спасибо,

Ответ 1

Apple Docs заявляет, что в iOS 11 есть новое объявление, которое может быть решением этой проблемы. В настоящее время iPhone X и iPhone 8 имеют один и тот же класс размеров, поэтому мы должны придумать другое решение.

var ДополнительныеSafeAreaInsets: UIEdgeInsets {get set}

Добавьте следующий код ниже в 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, совместимый с безопасным районом

enter image description here

Создатель интерфейса iPhone 8, совместимый с безопасным районом

enter image description here

Симулятор iPhone X - главный экран

enter image description here

iPhone X Simulator - подробный экран

enter image description here

Симулятор iPhone 8 - главный экран

enter image description here

iPhone 8 Simulator - подробный экран

enter image description here

Ответ 2

Еще один способ добиться этого прямо из раскадровки - создать два ограничения:
 1. Между вашим элементом и безопасной областью с приоритетом 250 с константой 0
 2. Между вашим элементом и дном суперпредставления с приоритетом 750 и константой 20 и отношением greater Than or Equal.

enter image description here

Ответ 3

Потратив немало времени, пытаясь решить проблему такого типа, изначально используя решение Marcos, я столкнулся с ситуацией, когда она не была решена - особенно в случае, когда "безопасная зона" была ненулевая высота, но не являющаяся безопасная область экрана означала, что смещение было 0 вместо минимального 20. Примером является контроллер произвольного вида с нижней безопасной областью, установленной через additionalSafeAreaInsets.

Решение состояло в том, чтобы проверить, выравнивается ли наш вид с окном с ненулевой безопасной областью, когда вставки безопасной области изменяются, и отрегулировать нижнее смещение ограничения на безопасную область, основываясь на этом. Следующее приводит к смещению на 20 пунктов снизу на экране в прямоangularьном стиле и 0 на полноэкранном экране в стиле безопасной зоны (iPhone X, последний iPad Pro, слайдеры iPad и т.д.).

// 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 safe area. The reason it the window rather than
    /// the screen is that on iPad, slide over apps and such also have this nonzero safe area. Basically anything that does not 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 does not have bottom safe area insets
        guard windowBottomSafeAreaInset > 0 else { return false }

        // Bail if our bottom area does not match the window 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

    }

}