Obtenez les hauteurs supérieures et inférieures de la zone de sécurité

178

Sur le nouvel iPhone X, quel serait le moyen le plus approprié d'obtenir la hauteur du haut et du bas pour les zones dangereuses?

entrez la description de l'image ici

Tulleb
la source

Réponses:

367

Essaye ça :

Dans l' objectif C

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

Dans Swift

if #available(iOS 11.0, *) {
    let window = UIApplication.shared.keyWindow
    let topPadding = window?.safeAreaInsets.top
    let bottomPadding = window?.safeAreaInsets.bottom
}
user6788419
la source
1
@KrutikaSonawala bottomPadding + 10 (votre valeur)
user6788419
12
Cela ne semble pas fonctionner pour moi - l'impression de la valeur de UIApplication.shared.keyWindow? .SafeAreaInsets renvoie tous les 0. Des idées?
Hai
2
Je l'ai fait, je peux contraindre safeAreaLayoutGuides de n'importe quelle vue, mais en imprimant directement la valeur de safeAreaInsets de la fenêtre clé, il renvoie simplement un rect zéro. Ceci est problématique car je dois contraindre manuellement une vue qui a été ajoutée à la fenêtre clé mais qui n'est pas dans la hiérarchie des vues du contrôleur de vue racine.
Hai
6
Pour moi, keyWindowc'était toujours nildonc je l'ai changé windows[0]et retiré du ?chaînage facultatif, puis cela a fonctionné.
George_E
2
Cela ne fonctionne que lorsque la vue d'un contrôleur est déjà apparue.
Vanya
85

Pour obtenir la hauteur entre les guides de mise en page, vous venez de faire

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

Alors full frame height = 812.0,safe area height = 734.0

Voici l'exemple où la vue verte a un cadre de guide.layoutFrame

entrez la description de l'image ici

Ladislav
la source
Merci, c'était le fil auquel je pensais aussi. Je ne pense pas qu'il existe une solution plus fiable. Mais avec cela, je n'obtiens que la hauteur non sûre complète, non? Pas deux hauteurs pour chaque zone?
Tulleb
2
Une fois que les vues ont été présentées, cela vous donnera la bonne réponse
Ladislav
2
En fait, la réponse de @ user6788419 a l'air bien aussi et beaucoup plus courte.
Tulleb
J'obtiens une hauteur de 812,0 avec ce code, en utilisant la vue d'un contrôleur. Donc j'utilise UIApplication.sharedApplication.keyWindow.safeAreaLayoutGuide.layoutFrame, qui a le cadre sûr.
Ferran Maylinch
1
@FerranMaylinch Je ne sais pas où vous vérifiez la hauteur, mais j'en avais aussi 812, ce qui s'est avéré être dû à la vérification de la valeur avant que la vue ne soit visible. Dans ce cas, les hauteurs seront les mêmes "Si la vue n'est pas actuellement installée dans une hiérarchie de vues, ou n'est pas encore visible à l'écran, les bords du guide de disposition sont égaux aux bords de la vue" developer.apple.com/documentation/ uikit / uiview /…
Nicholas Harlen
81

Swift 4, 5

L'épinglage d'une vue à une ancre de zone sûre à l'aide de contraintes peut être effectué n'importe où dans le cycle de vie du contrôleur de vue, car ils sont mis en file d'attente par l'API et gérés une fois la vue chargée en mémoire. Cependant, pour obtenir des valeurs de zone de sécurité, il faut attendre la fin du cycle de vie d'un contrôleur de vue, comme viewDidLayoutSubviews().

Cela se branche sur n'importe quel contrôleur de vue:

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
}

Je préfère cette méthode à la sortie de la fenêtre (si possible) car c'est ainsi que l'API a été conçue et, plus important encore, les valeurs sont mises à jour lors de tous les changements de vue, comme les changements d'orientation de l'appareil.

Cependant, certains contrôleurs de vue présentés personnalisés ne peuvent pas utiliser la méthode ci-dessus (je pense qu'ils sont dans des vues de conteneur transitoires). Dans de tels cas, vous pouvez obtenir les valeurs du contrôleur de vue racine, qui sera toujours disponible n'importe où dans le cycle de vie du contrôleur de vue actuel.

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
la source
3
Veuillez déclarer à la place en utilisant let topSafeArea: CGFloat!
Gusutafu
évitez d'utiliser viewDidLayoutSubviews(qui peut être appelé plusieurs fois), voici une solution qui fonctionnera même dans viewDidLoad: stackoverflow.com/a/53864017/7767664
user924
@ user924 alors les valeurs ne seront pas mises à jour lorsque les zones de sécurité changeront (c'est-à-dire la barre d'état à double hauteur)
bsod
@bsod alors ce sera mieux stackoverflow.com/a/3420550/7767664 (car ce n'est que pour la barre d'état et pas pour les autres vues)
user924
Ou au lieu de passer par le délégué d'application, vous pouvez simplement utiliser l'API de zone de sécurité Apple intégrée au contrôleur de vue.
bsod
21

Aucune des autres réponses ici n'a fonctionné pour moi, mais cela a fonctionné.

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
la source
1
Votre code fonctionnera dans n'importe quelle partie du cycle de vie du contrôleur de vue. Si vous souhaitez utiliser view.safeAreaInsets.top, vous devez vous assurer que vous l'appelez après dans viewDidAppear ou une version ultérieure. Dans viewDidLoad, vous n'obtiendrez pas le view.safeAreaInsets.top avec la valeur correcte car il n'est pas encore initialisé.
user3204765
1
meilleure réponse à ce jour
Rikco
17

Toutes les réponses ici sont utiles, merci à tous ceux qui ont offert leur aide.

Cependant, comme je vois que le sujet de la zone de sécurité est un peu confus, ce qui ne semble pas être bien documenté.

Je vais donc le résumer ici le plus possible pour le rendre facile à comprendre safeAreaInsets, safeAreaLayoutGuideet LayoutGuide.

Dans iOS 7, Apple a introduit les propriétés topLayoutGuideet bottomLayoutGuidedans UIViewController, Ils vous ont permis de créer des contraintes pour empêcher votre contenu d'être masqué par les barres UIKit comme l'état, la navigation ou la barre d'onglets Il était possible avec ces guides de mise en page de spécifier des contraintes sur le contenu, en l'évitant à masquer par les éléments de navigation du haut ou du bas (barres UIKit, barre d'état, barre de navigation ou d'onglets…).

Donc, par exemple, si vous voulez faire une tableView commence à partir de l'écran supérieur, vous avez fait quelque chose comme ça:

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

Dans iOS 11, Apple a désapprouvé ces propriétés en les remplaçant par un seul guide de disposition de zone de sécurité

Zone sûre selon Apple

Les zones sûres vous aident à placer vos vues dans la partie visible de l'interface globale. Les contrôleurs de vue définis par UIKit peuvent positionner des vues spéciales au-dessus de votre contenu. Par exemple, un contrôleur de navigation affiche une barre de navigation au-dessus du contenu du contrôleur de vue sous-jacent. Même lorsque ces vues sont partiellement transparentes, elles masquent toujours le contenu qui se trouve en dessous. Dans tvOS, la zone de sécurité comprend également les encarts de surbalayage de l'écran, qui représentent la zone couverte par le cadre de l'écran.

Ci-dessous, une zone de sécurité mise en évidence dans les séries iPhone 8 et iPhone X:

entrez la description de l'image ici

Le safeAreaLayoutGuideest une propriété deUIView

Pour obtenir la hauteur de safeAreaLayoutGuide:

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

Cela renverra la hauteur de la flèche dans votre image.

Maintenant, que diriez-vous d'obtenir les hauteurs d'indicateur «cran» supérieur et inférieur de l'écran d'accueil?

Ici, nous utiliserons le safeAreaInsets

La zone de sécurité d'une vue reflète la zone non couverte par les barres de navigation, les barres d'onglets, les barres d'outils et autres ancêtres qui obscurcissent la vue d'un contrôleur de vue. (Dans tvOS, la zone de sécurité reflète la zone non couverte par le cadre de l'écran.) Vous obtenez la zone de sécurité pour une vue en appliquant les encarts de cette propriété au rectangle des limites de la vue. Si la vue n'est pas actuellement installée dans une hiérarchie de vues ou n'est pas encore visible à l'écran, les encarts d'arête dans cette propriété sont 0.

Ce qui suit montre la zone dangereuse et la distance des bords sur l'iPhone 8 et l'un des iPhone X-Series.

entrez la description de l'image ici

entrez la description de l'image ici

Maintenant, si la barre de navigation est ajoutée

entrez la description de l'image ici

entrez la description de l'image ici

Alors, maintenant comment obtenir la hauteur de la zone dangereuse? nous utiliserons lesafeAreaInset

Voici les solutions mais elles diffèrent par une chose importante,

Premier:

self.view.safeAreaInsets

Cela renverra les EdgeInsets, vous pouvez maintenant accéder au haut et au bas pour connaître les inserts,

Deuxième:

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

Le premier, vous prenez les inserts de vue, donc s'il y a une barre de navigation, cela sera pris en compte, mais le second vous accédez aux safeAreaInsets de la fenêtre afin que la barre de navigation ne soit pas prise en compte

mojtaba al moussawi
la source
5

Dans iOS 11, il existe une méthode qui indique quand le safeArea a changé.

override func viewSafeAreaInsetsDidChange() {
    super.viewSafeAreaInsetsDidChange()
    let top = view.safeAreaInsets.top
    let bottom = view.safeAreaInsets.bottom
}
Martin
la source
3

Swift 5, Xcode 11.4

`UIApplication.shared.keyWindow` 

Cela donnera un avertissement de désapprobation. «keyWindow» était obsolète dans iOS 13.0: ne doit pas être utilisé pour les applications qui prennent en charge plusieurs scènes car il renvoie une fenêtre clé sur toutes les scènes connectées »en raison des scènes connectées. J'utilise de cette façon.

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 })
    }
}
user1039695
la source
2

Je travaille avec des cadres de CocoaPods et en cas UIApplication.sharedest disponible alors je l' utilise safeAreaInsetsen vue de window:

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

safeAreaLayoutGuide Lorsque la vue est visible à l'écran, ce guide reflète la partie de la vue qui n'est pas couverte par les barres de navigation, les barres d'onglets, les barres d'outils et autres vues des ancêtres. (Dans tvOS, la zone de sécurité reflète la zone non couverte par le cadre de l'écran.) Si la vue n'est pas actuellement installée dans une hiérarchie de vues, ou n'est pas encore visible à l'écran, les bords du guide de disposition sont égaux aux bords de la vue.

Ensuite, pour obtenir la hauteur de la flèche rouge dans la capture d'écran, c'est:

self.safeAreaLayoutGuide.layoutFrame.size.height
malhal
la source
1

Objective-C Qui a eu le problème lorsque keyWindow est égal à nil . Il suffit de mettre le code ci-dessus dans viewDidAppear (pas dans viewDidLoad)

DmitryForme
la source
1
UIWindow *window = [[[UIApplication sharedApplication] delegate] window]; 
CGFloat fBottomPadding = window.safeAreaInsets.bottom;
Peter
la source
1

Extension Swift 5

Cela peut être utilisé comme une extension et appelé avec: 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
    }
}

L'extension de UIApplication est facultative, peut être une extension de UIView ou tout ce qui est préféré, ou probablement encore mieux une fonction globale.

Théodore
la source
0

Une approche plus arrondie

  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
la source
0

Pour ceux d'entre vous qui passent en mode paysage, vous devez vous assurer d'utiliser viewSafeAreaInsetsDidChange après la rotation pour obtenir les valeurs les plus à jour:

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
la source