Pregunta sobre las peculiaridades de Autolayout del iPhone X.

Tengo dos botones, anteriormente estos estarían alineados en la parte inferior de la supervista con un desplazamiento de 20 para que no toquen la parte inferior de la pantalla (desde entonces he cambiado el enlace a Área segura, no a Supervista).

Aquí está la configuración original: iPhone 8 - Setup

Se ve bien como se esperaba en los iPhones más antiguos. iPhone 8 20 Offset

Ahora la constante 20 en la restricción inferior ahora hace que los botones se vean originales y demasiado lejos de la barra de inicio en el iPhone X. iPhone X 20 Offset

Lógicamente, necesito eliminar la constante 20 de la restricción en el iPhone X y alinear los botones directamente con la parte inferior del área segura. iPhone X - Setup

Se ve bien en iPhone X ahora. iPhone X 0 Offset

Pero ahora está colocando los botones demasiado cerca de la parte inferior de la pantalla en los teléfonos que no son de la barra de inicio. iPhone 8 0 Offset

¿Alguna solución inmediata a este problema que me perdí en los documentos de Apple? No se pueden usar las clases de tamaño ya que la clase de tamaño del iPhone X se superpone con los otros iPhones en este caso.

Pude codificar fácilmente para detectar iPhone X y establecer la constante en la restricción a 0, pero esperaba una solución más elegante.

Gracias

17
0000101010 23 oct. 2017 a las 03:06

3 respuestas

La mejor respuesta

Apple Docs afirma que hay una nueva declaración en iOS 11 que puede ser una solución a este problema. Actualmente, el iPhone X y el iPhone 8 comparten la misma clase de tamaño, por lo que debemos encontrar otra solución.

Var AdditionalSafeAreaInsets: UIEdgeInsets {get set}

Agregue el siguiente código a continuación en su AppDelegate y todos los hijos del rootViewController heredarán el área segura adicional. Las siguientes capturas de pantalla de ejemplo describen este comportamiento.

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 alineado al área segura

enter image description here

IPhone 8 Interface Builder alineado al área segura

enter image description here

Simulador de iPhone X - Pantalla maestra

enter image description here

IPhone X Simulator - Pantalla de detalles

enter image description here

Simulador de iPhone 8 - Pantalla maestra

enter image description here

Simulador de iPhone 8 - Pantalla de detalles

enter image description here

9
3 revs 25 oct. 2017 a las 22:15

Otra forma de lograr esto directamente desde el guión gráfico es crear dos restricciones:
1. Entre su elemento y área segura con prioridad 250 con 0 constante
2. Entre su elemento y el fondo de la supervista con prioridad 750 y 20 constantes y relación greater Than or Equal.

enter image description here

13
Marcos Griselli 5 dic. 2018 a las 14:43

Después de pasar bastante tiempo intentando solucionar este tipo de problema, originalmente usando la solución de Marcos, me encontré con un caso en el que no funcionaba no lo resuelva, específicamente en el caso de que el "área segura" sea una altura distinta de cero pero no sea el área segura de la pantalla significa que el desplazamiento fue 0 en lugar del mínimo 20. Ejemplo es un controlador de vista arbitrario con un área segura inferior configurada a través de additionalSafeAreaInsets.

La solución fue verificar si nuestra vista está alineada con la ventana con un área segura distinta de cero cuando las inserciones del área segura cambian, y ajustar el desplazamiento inferior de la restricción al área segura en función de eso. Lo siguiente conduce a un desplazamiento de 20 puntos desde la parte inferior en la pantalla de estilo rectangular, y 0 en una pantalla completa con pantalla de estilo de área segura (iPhone X, último iPad Pro, diapositivas de iPad, etc.).

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

    }

}
2
Ohifriend 27 sep. 2019 a las 03:36