Obtenga un área segura insertando las alturas superior e inferior

178

En el nuevo iPhone X, ¿cuál sería la forma más adecuada de obtener la altura superior e inferior para las áreas inseguras?

ingrese la descripción de la imagen aquí

Tulleb
fuente

Respuestas:

367

Prueba esto :

En el objetivo C

if (@available(iOS 11.0, *)) {
    UIWindow *window = UIApplication.sharedApplication.keyWindow;
    CGFloat topPadding = window.safeAreaInsets.top;
    CGFloat bottomPadding = window.safeAreaInsets.bottom;
}

En veloz

if #available(iOS 11.0, *) {
    let window = UIApplication.shared.keyWindow
    let topPadding = window?.safeAreaInsets.top
    let bottomPadding = window?.safeAreaInsets.bottom
}
usuario6788419
fuente
1
@ KrutikaSonawala bottomPadding + 10 (su valor)
usuario6788419
12
Esto no parece funcionar para mí: imprimir el valor de UIApplication.shared.keyWindow? .SafeAreaInsets devuelve todos los ceros. ¿Algunas ideas?
Hai
2
Lo hice, puedo restringir contra safeAreaLayoutGuides de cualquier vista, pero imprimir directamente el valor de safeAreaInsets de la ventana clave solo devuelve un rect cero. Esto es problemático porque necesito restringir manualmente una vista que se ha agregado a la ventana clave pero que no está en la jerarquía de vistas del controlador de vista raíz.
Hai
66
Para mí, keyWindowsiempre fue nilasí que lo cambié windows[0]y lo eliminé del ?encadenamiento opcional, luego funcionó.
George_E
2
Solo funciona cuando ya ha aparecido la vista de un controlador.
Vanya
85

Para obtener la altura entre las guías de diseño, simplemente haz

let guide = view.safeAreaLayoutGuide
let height = guide.layoutFrame.size.height

Por lo tanto full frame height = 812.0,safe area height = 734.0

A continuación se muestra el ejemplo donde la vista verde tiene un marco de guide.layoutFrame

ingrese la descripción de la imagen aquí

Ladislav
fuente
Gracias, ese era el plomo en el que también estaba pensando. No creo que haya una solución más confiable. Pero con esto solo obtengo la altura insegura completa, ¿verdad? ¿No hay dos alturas para cada área?
Tulleb
2
Una vez que se hayan presentado las opiniones, esto le dará la respuesta correcta
Ladislav,
2
En realidad, la respuesta de @ user6788419 también se ve bien y mucho más corta.
Tulleb
Estoy obteniendo una altura de 812.0 con ese código, usando la vista de un controlador. Así que estoy usando UIApplication.sharedApplication.keyWindow.safeAreaLayoutGuide.layoutFrame, que tiene el marco seguro.
Ferran Maylinch
1
@FerranMaylinch No estoy seguro de dónde está verificando la altura, pero también tenía 812, que resultó ser debido a la verificación del valor antes de que la vista fuera visible. En ese caso, las alturas serán las mismas "Si la vista no está instalada actualmente en una jerarquía de vistas o aún no está visible en la pantalla, los bordes de la guía de diseño son iguales a los bordes de la vista" developer.apple.com/documentation/ uikit / uiview / ...
Nicholas Harlen
81

Swift 4, 5

Para anclar una vista a un ancla de área segura, se pueden usar restricciones en cualquier parte del ciclo de vida del controlador de vista porque la API las pone en cola y las maneja después de que la vista se haya cargado en la memoria. Sin embargo, obtener valores de área segura requiere esperar hacia el final del ciclo de vida de un controlador de vista, como viewDidLayoutSubviews().

Esto se conecta a cualquier controlador de vista:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let topSafeArea: CGFloat
    let bottomSafeArea: CGFloat

    if #available(iOS 11.0, *) {
        topSafeArea = view.safeAreaInsets.top
        bottomSafeArea = view.safeAreaInsets.bottom
    } else {
        topSafeArea = topLayoutGuide.length
        bottomSafeArea = bottomLayoutGuide.length
    }

    // safe area values are now available to use
}

Prefiero este método a sacarlo de la ventana (cuando sea posible) porque es cómo se diseñó la API y, lo que es más importante, los valores se actualizan durante todos los cambios de vista, como los cambios de orientación del dispositivo.

Sin embargo, algunos controladores de vista personalizados no pueden usar el método anterior (sospecho que están en vistas de contenedor transitorio). En tales casos, puede obtener los valores del controlador de vista raíz, que siempre estará disponible en cualquier parte del ciclo de vida del controlador de vista actual.

anyLifecycleMethod()
    guard let root = UIApplication.shared.keyWindow?.rootViewController else {
        return
    }
    let topSafeArea: CGFloat
    let bottomSafeArea: CGFloat

    if #available(iOS 11.0, *) {
        topSafeArea = root.view.safeAreaInsets.top
        bottomSafeArea = root.view.safeAreaInsets.bottom
    } else {
        topSafeArea = root.topLayoutGuide.length
        bottomSafeArea = root.bottomLayoutGuide.length
    }

    // safe area values are now available to use
}
bsod
fuente
3
Por favor, declare usando let topSafeArea: CGFloat en su lugar.
Gusutafu
evite usar viewDidLayoutSubviews(que se puede llamar varias veces), aquí hay una solución que funcionará incluso en viewDidLoad: stackoverflow.com/a/53864017/7767664
user924
@ user924, entonces los valores no se actualizarán cuando cambien las áreas seguras (es decir, la barra de estado de doble altura)
bsod
@bsod, entonces será mejor stackoverflow.com/a/3420550/7767664 (porque es solo para la barra de estado y no para otras vistas)
usuario924
O en lugar de pasar por el delegado de la aplicación, puede usar el área segura API de Apple integrada en el controlador de vista.
bsod
21

Ninguna de las otras respuestas aquí funcionó para mí, pero esto sí.

var topSafeAreaHeight: CGFloat = 0
var bottomSafeAreaHeight: CGFloat = 0

  if #available(iOS 11.0, *) {
    let window = UIApplication.shared.windows[0]
    let safeFrame = window.safeAreaLayoutGuide.layoutFrame
    topSafeAreaHeight = safeFrame.minY
    bottomSafeAreaHeight = window.frame.maxY - safeFrame.maxY
  }
ScottyBlades
fuente
1
Su código funcionará en cualquier parte del ciclo de vida del controlador de vista. Si desea usar view.safeAreaInsets.top, debe asegurarse de llamarlo después en viewDidAppear o más tarde. En viewDidLoad, no obtendrá view.safeAreaInsets.top con el valor correcto porque aún no se ha inicializado.
user3204765
1
mejor respuesta hasta ahora
Rikco
17

Todas las respuestas aquí son útiles, gracias a todos los que ofrecieron ayuda.

Sin embargo, como veo que el tema del área segura está un poco confundido, lo que no parece estar bien documentado.

Así que lo resumiré aquí lo más rápido posible para que sea fácil de entender safeAreaInsets, safeAreaLayoutGuideyLayoutGuide .

En iOS 7, Apple introdujo las propiedades topLayoutGuidey bottomLayoutGuideenUIViewController , le permitieron crear restricciones para evitar que su contenido se ocultara en barras UIKit como el estado, la navegación o la barra de pestañas. Con estas guías de diseño era posible especificar restricciones en el contenido, evitándolo estar oculto por los elementos de navegación superior o inferior (barras UIKit, barra de estado, barra de navegación o pestaña ...).

Entonces, por ejemplo, si quieres hacer que una vista de tabla comience desde la pantalla superior, has hecho algo así:

self.tableView.contentInset = UIEdgeInsets(top: -self.topLayoutGuide.length, left: 0, bottom: 0, right: 0)

En iOS 11, Apple ha desaprobado estas propiedades reemplazándolas con una sola guía de diseño de área segura

Área segura según Apple

Las áreas seguras lo ayudan a colocar sus vistas dentro de la parte visible de la interfaz general. Los controladores de vista definidos por UIKit pueden colocar vistas especiales en la parte superior de su contenido. Por ejemplo, un controlador de navegación muestra una barra de navegación en la parte superior del contenido del controlador de vista subyacente. Incluso cuando tales vistas son parcialmente transparentes, aún ocluyen el contenido que está debajo de ellas. En tvOS, el área segura también incluye las inserciones de sobreexploración de la pantalla, que representan el área cubierta por el bisel de la pantalla.

A continuación, un área segura resaltada en iPhone 8 y iPhone X-series:

ingrese la descripción de la imagen aquí

El safeAreaLayoutGuidees una propiedad deUIView

Para obtener la altura de safeAreaLayoutGuide:

extension UIView {
   var safeAreaHeight: CGFloat {
       if #available(iOS 11, *) {
        return safeAreaLayoutGuide.layoutFrame.size.height
       }
       return bounds.height
  }
}

Eso devolverá la altura de la flecha en su imagen.

Ahora, ¿qué pasa con la obtención de las alturas de los indicadores de "muesca" superior e inferior de la pantalla de inicio?

Aquí usaremos el safeAreaInsets

El área segura de una vista refleja el área no cubierta por barras de navegación, barras de pestañas, barras de herramientas y otros antepasados ​​que oscurecen la vista de un controlador de vista. (En tvOS, el área segura refleja el área no cubierta por el bisel de la pantalla). Usted obtiene el área segura para una vista aplicando las inserciones de esta propiedad al rectángulo de límites de la vista. Si la vista no está instalada actualmente en una jerarquía de vistas, o aún no está visible en la pantalla, las inserciones de borde en esta propiedad son 0.

A continuación se mostrará el área insegura y la distancia desde los bordes en el iPhone 8 y uno de la serie X del iPhone.

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Ahora, si se agrega la barra de navegación

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Entonces, ¿cómo obtener la altura del área insegura? usaremos elsafeAreaInset

Aquí hay soluciones, sin embargo, difieren en algo importante,

El primero:

self.view.safeAreaInsets

Eso devolverá los EdgeInsets, ahora puede acceder a la parte superior e inferior para conocer las inserciones,

Segundo:

UIApplication.shared.windows.first{$0.isKeyWindow }?.safeAreaInsets

El primero está tomando los recuadros de vista, por lo que si hay una barra de navegación se considerará, sin embargo, el segundo está accediendo a safeAreaInsets de la ventana, por lo que la barra de navegación no se considerará

mojtaba al moussawi
fuente
5

En iOS 11 hay un método que indica cuándo SafeArea ha cambiado.

override func viewSafeAreaInsetsDidChange() {
    super.viewSafeAreaInsetsDidChange()
    let top = view.safeAreaInsets.top
    let bottom = view.safeAreaInsets.bottom
}
Martín
fuente
3

Swift 5, Xcode 11.4

`UIApplication.shared.keyWindow` 

Dará una advertencia de desaprobación. 'keyWindow' quedó en desuso en iOS 13.0: no debe usarse para aplicaciones que admitan varias escenas, ya que devuelve una ventana clave en todas las escenas conectadas 'debido a las escenas conectadas. Yo uso de esta manera.

extension UIView {

    var safeAreaBottom: CGFloat {
         if #available(iOS 11, *) {
            if let window = UIApplication.shared.keyWindowInConnectedScenes {
                return window.safeAreaInsets.bottom
            }
         }
         return 0
    }

    var safeAreaTop: CGFloat {
         if #available(iOS 11, *) {
            if let window = UIApplication.shared.keyWindowInConnectedScenes {
                return window.safeAreaInsets.top
            }
         }
         return 0
    }
}

extension UIApplication {
    var keyWindowInConnectedScenes: UIWindow? {
        return windows.first(where: { $0.isKeyWindow })
    }
}
usuario1039695
fuente
2

Estoy trabajando con los marcos CocoaPods y, en caso de UIApplication.sharedque no esté disponible , lo uso safeAreaInsetsen view window:

if #available(iOS 11.0, *) {
    let insets = view.window?.safeAreaInsets
    let top = insets.top
    let bottom = insets.bottom
}
Tai Le
fuente
2

safeAreaLayoutGuide Cuando la vista es visible en pantalla, esta guía refleja la parte de la vista que no está cubierta por barras de navegación, barras de pestañas, barras de herramientas y otras vistas de antepasados. (En tvOS, el área segura refleja el área que no cubre el bisel de la pantalla). Si la vista no está instalada actualmente en una jerarquía de vistas o aún no está visible en la pantalla, los bordes de la guía de diseño son iguales a los bordes de la vista.

Luego, para obtener la altura de la flecha roja en la captura de pantalla, es:

self.safeAreaLayoutGuide.layoutFrame.size.height
malhal
fuente
1

Objective-C ¿Quién tuvo el problema cuando keyWindow es igual a nil ? Simplemente ponga el código de arriba en viewDidAppear (no en viewDidLoad)

DmitryShapkin
fuente
1
UIWindow *window = [[[UIApplication sharedApplication] delegate] window]; 
CGFloat fBottomPadding = window.safeAreaInsets.bottom;
Peter
fuente
1

Extensión Swift 5

Esto se puede usar como una extensión y llamar con: UIApplication.topSafeAreaHeight

extension UIApplication {
    static var topSafeAreaHeight: CGFloat {
        var topSafeAreaHeight: CGFloat = 0
         if #available(iOS 11.0, *) {
               let window = UIApplication.shared.windows[0]
               let safeFrame = window.safeAreaLayoutGuide.layoutFrame
               topSafeAreaHeight = safeFrame.minY
             }
        return topSafeAreaHeight
    }
}

La extensión de la aplicación UIA es opcional, puede ser una extensión de UIView o lo que sea preferido, o probablemente incluso una función global.

Theodore
fuente
0

Un enfoque más redondeado

  import SnapKit

  let containerView = UIView()
  containerView.backgroundColor = .red
  self.view.addSubview(containerView)
  containerView.snp.remakeConstraints { (make) -> Void in
        make.width.top.equalToSuperView()
        make.top.equalTo(self.view.safeArea.top)
        make.bottom.equalTo(self.view.safeArea.bottom)
  }




extension UIView {
    var safeArea: ConstraintBasicAttributesDSL {
        if #available(iOS 11.0, *) {
            return self.safeAreaLayoutGuide.snp
        }
        return self.snp
    }


    var isIphoneX: Bool {

        if #available(iOS 11.0, *) {
            if topSafeAreaInset > CGFloat(0) {
                return true
            } else {
                return false
            }
        } else {
            return false
        }

    }

    var topSafeAreaInset: CGFloat {
        let window = UIApplication.shared.keyWindow
        var topPadding: CGFloat = 0
        if #available(iOS 11.0, *) {
            topPadding = window?.safeAreaInsets.top ?? 0
        }

        return topPadding
    }

    var bottomSafeAreaInset: CGFloat {
        let window = UIApplication.shared.keyWindow
        var bottomPadding: CGFloat = 0
        if #available(iOS 11.0, *) {
            bottomPadding = window?.safeAreaInsets.bottom ?? 0
        }

        return bottomPadding
    }
}
johndpope
fuente
0

Para aquellos de ustedes que cambian al modo horizontal, deben asegurarse de usar viewSafeAreaInsetsDidChange después de la rotación para obtener los valores más actualizados:

private var safeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

override func viewSafeAreaInsetsDidChange() {
        if #available(iOS 11.0, *) {
            safeAreaInsets = UIApplication.shared.keyWindow!.safeAreaInsets
        }
}
Oz Shabat
fuente