Obtenha áreas seguras nas alturas superior e inferior

178

No novo iPhone X, qual seria a maneira mais adequada de obter altura superior e inferior para as áreas inseguras?

insira a descrição da imagem aqui

Tulleb
fonte

Respostas:

366

Tente o seguinte:

No objetivo C

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

Na Swift

if #available(iOS 11.0, *) {
    let window = UIApplication.shared.keyWindow
    let topPadding = window?.safeAreaInsets.top
    let bottomPadding = window?.safeAreaInsets.bottom
}
user6788419
fonte
1
@KrutikaSonawala bottomPadding + 10 (your value)
user6788419
12
Isso não parece funcionar para mim - imprimir o valor de UIApplication.shared.keyWindow? .SafeAreaInsets retorna todos os 0s. Alguma ideia?
Hai
2
Sim, posso restringir o safeAreaLayoutGuides de qualquer visualização, mas imprimir diretamente o valor do safeAreaInsets da janela da chave retorna apenas um retângulo zero. Isso é problemático porque preciso restringir manualmente uma exibição que foi adicionada à janela principal, mas que não está na hierarquia de exibição do controlador de exibição raiz.
Hai
6
Para mim, keyWindowsempre foi nilassim que eu mudei windows[0]e removi o ?encadeamento opcional e funcionou.
George_E
2
Está funcionando apenas quando a exibição de um controlador já apareceu.
Vanya
85

Para obter a altura entre as guias de layout, basta

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

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

Abaixo está o exemplo em que a vista verde possui um quadro de guide.layoutFrame

insira a descrição da imagem aqui

Ladislav
fonte
Obrigado, essa foi a liderança em que eu também estava pensando. Eu não acho que exista uma solução mais confiável. Mas com isso eu só tenho toda a altura insegura, certo? Não há duas alturas para cada área?
Tulleb
2
Uma vez vistas foram definidos isso vai lhe dar a resposta correta
Ladislav
2
Na verdade, a resposta de @ user6788419 também parece boa e muito mais curta.
Tulleb
Estou obtendo uma altura de 812.0 com esse código, usando a exibição de um controlador. Então, eu estou usando UIApplication.sharedApplication.keyWindow.safeAreaLayoutGuide.layoutFrame, que tem o quadro seguro.
Ferran Maylinch 7/03/19
1
@FerranMaylinch Não tenho certeza de onde você está verificando a altura, mas eu também tinha o 812, que acabou por ser devido à verificação do valor antes da visualização ser visível. Nesse caso, as alturas serão as mesmas "Se a visualização não estiver atualmente instalada em uma hierarquia de visualizações ou ainda não estiver visível na tela, as arestas da guia de layout serão iguais às arestas da visualização" developer.apple.com/documentation/ uikit / uiview /…
Nicholas Harlen
81

Swift 4, 5

Para fixar uma exibição em uma âncora de área segura, o uso de restrições pode ser feito em qualquer lugar do ciclo de vida do controlador de exibição, porque eles são enfileirados pela API e manipulados após o carregamento da exibição na memória. No entanto, para obter valores de área segura, é necessário aguardar o final do ciclo de vida de um controlador de exibição, comoviewDidLayoutSubviews() .

Isso se conecta a qualquer controlador de exibição:

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
}

Prefiro esse método a tirá-lo da janela (quando possível) porque é como a API foi projetada e, mais importante, os valores são atualizados durante todas as alterações de exibição, como alterações na orientação do dispositivo.

No entanto, alguns controladores de exibição apresentados personalizados não podem usar o método acima (suspeito porque estão em visualizações de contêiner transitório). Nesses casos, é possível obter os valores do controlador de exibição raiz, que sempre estará disponível em qualquer lugar do ciclo de vida do controlador de exibição atual.

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
fonte
3
Declare usando let topSafeArea: CGFloat!
Gusutafu
evite usar viewDidLayoutSubviews(que pode ser chamado várias vezes), aqui está uma solução que funcionará mesmo em viewDidLoad: stackoverflow.com/a/53864017/7767664
user924
@ user924 então os valores não serão atualizados quando a mudança segura áreas (por exemplo, a barra de status de altura dupla)
BSOD
@bsod então isso vai ser melhor stackoverflow.com/a/3420550/7767664 (porque é apenas para barra de status e não de outros pontos de vista)
user924
Ou, em vez de consultar o representante do aplicativo, você pode apenas usar a API da área segura que a Apple incorporou ao controlador de exibição.
BSOD
21

Nenhuma das outras respostas aqui funcionou para mim, mas isso funcionou.

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
fonte
1
Seu código funcionará em qualquer parte do ciclo de vida do controlador de exibição. Se você deseja usar o view.safeAreaInsets.top, certifique-se de chamá-lo depois no viewDidAppear ou posterior. Em viewDidLoad, você não obterá o view.safeAreaInsets.top com o valor correto, porque ele ainda não foi inicializado.
user3204765
1
melhor resposta até agora
Rikco 12/12/19
17

Todas as respostas aqui são úteis. Obrigado a todos que ofereceram ajuda.

No entanto, como vejo que o tópico da área de segurança está um pouco confuso, o que não parece estar bem documentado.

Então eu vou resumi-lo aqui como mush possível para torná-lo fácil de compreender safeAreaInsets, safeAreaLayoutGuidee LayoutGuide.

No iOS 7, a Apple introduziu as propriedades topLayoutGuidee , Eles permitiram criar restrições para impedir que seu conteúdo fosse oculto pelas barras do UIKit, como status, navegação ou barra de guias. oculto pelos elementos de navegação superior ou inferior (barras do UIKit, barra de status, barra de navegação ou tabulação…).bottomLayoutGuideUIViewController

Então, por exemplo, se você deseja criar um tableView a partir da tela superior, você fez algo assim:

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

No iOS 11, a Apple substituiu essas propriedades, substituindo-as por um único guia de layout de área segura

Área segura de acordo com a Apple

As áreas seguras ajudam a colocar suas visualizações na parte visível da interface geral. Os controladores de exibição definidos pelo UIKit podem posicionar visualizações especiais sobre o seu conteúdo. Por exemplo, um controlador de navegação exibe uma barra de navegação na parte superior do conteúdo do controlador de exibição subjacente. Mesmo quando essas visualizações são parcialmente transparentes, elas ainda ocultam o conteúdo que está abaixo delas. No tvOS, a área segura também inclui as inserções de overscan da tela, que representam a área coberta pelo painel da tela.

Abaixo, uma área segura destacada no iPhone 8 e iPhone X-series:

insira a descrição da imagem aqui

O safeAreaLayoutGuideé uma propriedade deUIView

Para obter a altura de safeAreaLayoutGuide:

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

Isso retornará a altura da seta na sua foto.

Agora, que tal obter as alturas dos indicadores superiores e inferiores da tela inicial?

Aqui vamos usar o safeAreaInsets

A área segura de uma exibição reflete a área não coberta por barras de navegação, guias, barras de ferramentas e outros ancestrais que obscurecem a exibição de um controlador de exibição. (No tvOS, a área segura reflete a área não coberta pelo painel da tela.) Você obtém a área segura para uma vista aplicando as inserções nessa propriedade ao retângulo de limites da vista. Se a visualização não estiver instalada no momento em uma hierarquia de visualização ou ainda não estiver visível na tela, as inserções de borda nesta propriedade serão 0.

A seguir, será mostrada a área insegura e a distância das bordas do iPhone 8 e de um do iPhone X-Series.

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Agora, se a barra de navegação tiver sido adicionada

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Então, agora como obter a altura da área insegura? vamos usar osafeAreaInset

Aqui estão as soluções, porém elas diferem em algo importante,

Primeiro:

self.view.safeAreaInsets

Isso retornará os EdgeInsets. Agora você pode acessar a parte superior e a parte inferior para conhecer as inserções,

O segundo:

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

O primeiro que você está visualizando, portanto, se houver uma barra de navegação, ela será considerada; no entanto, o segundo, você estará acessando o safeAreaInsets da janela, para que a barra de navegação não seja considerada

mojtaba al moussawi
fonte
5

No iOS 11, existe um método que informa quando a área segura foi alterada.

override func viewSafeAreaInsetsDidChange() {
    super.viewSafeAreaInsetsDidChange()
    let top = view.safeAreaInsets.top
    let bottom = view.safeAreaInsets.bottom
}
Martin
fonte
2

Eu estou trabalhando com estruturas CocoaPods e no caso UIApplication.sharedé indisponíveis então eu uso safeAreaInsetsna visão de window:

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

safeAreaLayoutGuide Quando a exibição é visível na tela, este guia reflete a parte da exibição que não é coberta por barras de navegação, guias, barras de ferramentas e outras exibições ancestrais. (No tvOS, a área segura reflete a área não coberta pelo painel da tela.) Se a visualização não estiver atualmente instalada em uma hierarquia de visualizações ou ainda não estiver visível na tela, as bordas da guia de layout serão iguais às bordas da visualização.

Então, para obter a altura da seta vermelha na captura de tela, é:

self.safeAreaLayoutGuide.layoutFrame.size.height
malhal
fonte
2

Swift 5, Xcode 11.4

`UIApplication.shared.keyWindow` 

Isso dará um aviso de descontinuação. '' keyWindow 'foi descontinuado no iOS 13.0: não deve ser usado para aplicativos que suportam várias cenas, pois retorna uma janela de chave em todas as cenas conectadas' por causa das cenas conectadas. Eu uso assim.

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
fonte
1

Objetivo-C Quem teve o problema quando keyWindow é igual a zero . Basta colocar o código acima em viewDidAppear (não em viewDidLoad)

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

Extensão Swift 5

Isso pode ser usado como uma extensão e chamado com: 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
    }
}

A extensão do UIApplication é opcional, pode ser uma extensão do UIView ou o que for preferido, ou provavelmente uma função global ainda melhor.

Theodore
fonte
0

Uma abordagem mais arredondada

  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
fonte
0

Para aqueles que mudam para o modo paisagem, use o viewSafeAreaInsetsDidChange após a rotação para obter os valores mais atualizados:

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
fonte