Como uso o Layout da área segura de maneira programática?

99

Como não uso storyboards para criar minhas visualizações, gostaria de saber se existe a opção "Usar guias de área segura" programaticamente ou algo parecido.

Eu tentei ancorar minhas visões em

view.safeAreaLayoutGuide

mas eles continuam se sobrepondo ao topo do simulador do iPhone X.

Phillip
fonte
1
Nenhuma propriedade bool documentada sobre isso de acordo com: developer.apple.com/documentation/uikit/uiview/…
dvp.petrov
Que tal view.safeAreaInsets? Você tentou isso?
Karthikeyan Bose
@KarthikeyanBose sim, mas não tive sorte, infelizmente.
Phillip de
funciona para mim. Como é o código
Daniel Springer

Respostas:

155

Aqui está um código de amostra (Ref de: Guia de layout de área segura ):
Se você criar suas restrições no código, use a propriedade safeAreaLayoutGuide de UIView para obter as âncoras de layout relevantes. Vamos recriar o exemplo do Interface Builder acima no código para ver como fica:

Supondo que temos a visão verde como uma propriedade em nosso controlador de visão:

private let greenView = UIView()

Podemos ter uma função para configurar as visualizações e restrições chamadas de viewDidLoad:

private func setupView() {
  greenView.translatesAutoresizingMaskIntoConstraints = false
  greenView.backgroundColor = .green
  view.addSubview(greenView)
}

Crie as restrições de margem esquerda e direita como sempre usando o layoutMarginsGuide da visualização raiz:

 let margins = view.layoutMarginsGuide
 NSLayoutConstraint.activate([
    greenView.leadingAnchor.constraint(equalTo: margins.leadingAnchor),
    greenView.trailingAnchor.constraint(equalTo: margins.trailingAnchor)
 ])

Agora, a menos que você esteja visando o iOS 11 e posterior, você precisará envolver as restrições do guia de layout da área segura com #available e voltar aos guias de layout superior e inferior para versões anteriores do iOS:

if #available(iOS 11, *) {
  let guide = view.safeAreaLayoutGuide
  NSLayoutConstraint.activate([
   greenView.topAnchor.constraintEqualToSystemSpacingBelow(guide.topAnchor, multiplier: 1.0),
   guide.bottomAnchor.constraintEqualToSystemSpacingBelow(greenView.bottomAnchor, multiplier: 1.0)
   ])
} else {
   let standardSpacing: CGFloat = 8.0
   NSLayoutConstraint.activate([
   greenView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: standardSpacing),
   bottomLayoutGuide.topAnchor.constraint(equalTo: greenView.bottomAnchor, constant: standardSpacing)
   ])
}

Resultado:

insira a descrição da imagem aqui

insira a descrição da imagem aqui


Aqui está a documentação oficial do desenvolvedor da Apple para o guia de layout de área segura


A área de segurança é necessária para lidar com o design da interface do usuário para iPhone-X. Aqui estão as diretrizes básicas para como projetar a interface do usuário para iPhone-X usando o Layout de área segura

Krunal
fonte
2
Seria possível dar isso no objetivo-C também? Parece exatamente o que eu preciso
Tom Hammond
6
@TomHammond Aqui está em Objective-C para você stackoverflow.com/a/47076040/5638630
Krunal
2
@ZonilyJame Agora sobre sua consulta - SaFeAreaLayout é uma estrutura específica do iOS (não específica do dispositivo iPhoneX), está substituindo o guia de Layout Top-Bottom no iOS 11, portanto, devemos usar / definir a condição para iOS, não para o dispositivo. SafeAreaLayout cuida dos designs para todos os tipos de dispositivos (iPhone-X e outros). Você pode me pedir mais detalhes, se ainda tiver alguma dúvida / confusão.
Krunal de
3
Impressionante. Obrigado :)
Rajamohan S
1
por que é aceita como resposta correta? Tentei configurar uma posição concreta - o resultado é totalmente aleatório - o controle é colocado em uma posição imprevisível!
Vyachaslav Gerchicov
82

Na verdade, estou usando uma extensão para ele e controlando se é ios 11 ou não.

extension UIView {

  var safeTopAnchor: NSLayoutYAxisAnchor {
    if #available(iOS 11.0, *) {
      return self.safeAreaLayoutGuide.topAnchor
    }
    return self.topAnchor
  }

  var safeLeftAnchor: NSLayoutXAxisAnchor {
    if #available(iOS 11.0, *){
      return self.safeAreaLayoutGuide.leftAnchor
    }
    return self.leftAnchor
  }

  var safeRightAnchor: NSLayoutXAxisAnchor {
    if #available(iOS 11.0, *){
      return self.safeAreaLayoutGuide.rightAnchor
    }
    return self.rightAnchor
  }

  var safeBottomAnchor: NSLayoutYAxisAnchor {
    if #available(iOS 11.0, *) {
      return self.safeAreaLayoutGuide.bottomAnchor
    }
    return self.bottomAnchor
  }
}
Alper
fonte
É uma maneira tão simples de fazer e funciona. Obrigado pela ideia.
alper_k
Esta é uma maneira tão simples, mas agradável de fazer isso! Observe o uso de em self.safeAreaLayoutGuidevez de self.layoutMarginsGuide. O seguro usado nesta resposta funcionou corretamente para eu ficar dentro da área segura! Uma coisa que eu sugeriria mudar seria usar leadingAnchore em trailingAnchorvez de leftAnchore rightAnchor. Bravo!
Pranoy C de
22

SafeAreaLayoutGuideé UIViewpropriedade,

A parte superior do safeAreaLayoutGuide indica a borda superior não obscurecida da visualização (por exemplo, não atrás da barra de status ou da barra de navegação, se houver). Da mesma forma para as outras bordas.

Use safeAreaLayoutGuidepara evitar que nossos objetos sejam cortados / sobrepostos a partir de cantos arredondados, barras de navegação, barras de guias, barras de ferramentas e outras visualizações ancestrais.

Podemos criar safeAreaLayoutGuideobjeto e definir restrições de objeto, respectivamente.

As restrições para retrato + paisagem são -

Imagem de retrato

Imagem paisagem

        self.edgesForExtendedLayout = []//Optional our as per your view ladder

        let newView = UIView()
        newView.backgroundColor = .red
        self.view.addSubview(newView)
        newView.translatesAutoresizingMaskIntoConstraints = false
        if #available(iOS 11.0, *) {
            let guide = self.view.safeAreaLayoutGuide
            newView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
            newView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
            newView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
            newView.heightAnchor.constraint(equalToConstant: 100).isActive = true

        }
        else {
            NSLayoutConstraint(item: newView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 0).isActive = true
            NSLayoutConstraint(item: newView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0).isActive = true
            NSLayoutConstraint(item: newView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1.0, constant: 0).isActive = true

            newView.heightAnchor.constraint(equalToConstant: 100).isActive = true
        }

UILayoutGuide

safeAreaLayoutGuide

Jack
fonte
2
Nunca, jamais faça restrições de configuração no viewDidAppear, a menos que você saiba absolutamente o que está fazendo. viewDidAppearé chamado várias vezes e, portanto, suas restrições serão duplicadas toda vez que for chamado.
Yevhen Dubinin
Sim! , Resposta editada, você pode usar como seu caso de uso.
Jack
14

Para aqueles de vocês que usam o SnapKit , assim como eu, a solução é ancorar suas restrições para view.safeAreaLayoutGuide:

yourView.snp.makeConstraints { (make) in
    if #available(iOS 11.0, *) {
        //Bottom guide
        make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottomMargin)
        //Top guide
        make.top.equalTo(view.safeAreaLayoutGuide.snp.topMargin)
        //Leading guide
        make.leading.equalTo(view.safeAreaLayoutGuide.snp.leadingMargin)
        //Trailing guide
        make.trailing.equalTo(view.safeAreaLayoutGuide.snp.trailingMargin)

     } else {
        make.edges.equalToSuperview()
     }
}
Phillip
fonte
1
Ótima resposta. Para torná-lo um pouco mais compacto, você poderia dizer: if #available (iOS 11.0, *) {make.edges.equalTo (view.safeAreaLayoutGuide.snp.margins)}
Don Miguel
7

Estou usando isso em vez de adicionar restrições de margem inicial e final ao layoutMarginsGuide:

UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
yourView.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
                                           [safe.trailingAnchor constraintEqualToAnchor:yourView.trailingAnchor],
                                           [yourView.leadingAnchor constraintEqualToAnchor:safe.leadingAnchor],
                                           [yourView.topAnchor constraintEqualToAnchor:safe.topAnchor],
                                           [safe.bottomAnchor constraintEqualToAnchor:yourView.bottomAnchor]
                                          ]];

Verifique também a opção de versão inferior do ios 11 na resposta de Krunal.

Tony TRAN
fonte
Certifique-se de que já adicionou yourView ao superView. É self.view No meu código como um exemplo simples.
Tony TRAN
6

Use UIWindowou UIView'ssafeAreaInsets .bottom .top .left .right

// #available(iOS 11.0, *)
// height - UIApplication.shared.keyWindow!.safeAreaInsets.bottom

// On iPhoneX
// UIApplication.shared.keyWindow!.safeAreaInsets.top =  44
// UIApplication.shared.keyWindow!.safeAreaInsets.bottom = 34

// Other devices
// UIApplication.shared.keyWindow!.safeAreaInsets.top =  0
// UIApplication.shared.keyWindow!.safeAreaInsets.bottom = 0

// example
let window = UIApplication.shared.keyWindow!
let viewWidth = window.frame.size.width
let viewHeight = window.frame.size.height - window.safeAreaInsets.bottom
let viewFrame = CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight)
let aView = UIView(frame: viewFrame)
aView.backgroundColor = .red
view.addSubview(aView)
aView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
Warif Akhand Rishi
fonte
3
Este é o caminho a percorrer se a sua visualização não estiver usando o AutoLayout.
Jonathan Cabrera
4

Use restrições com formato visual e você terá respeito pela área segura gratuitamente.

class ViewController: UIViewController {

    var greenView = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()
        greenView.backgroundColor = .green
        view.addSubview(greenView)
    }
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        greenView.translatesAutoresizingMaskIntoConstraints = false
        let views : [String:Any] = ["greenView":greenView]
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[greenView]-|", options: [], metrics: nil, views: views))
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[greenView]-|", options: [], metrics: nil, views: views))
    }
}

resultado

AtomicBoolean
fonte
2
Forneça um comentário ao votar contra, para que possamos saber se houver um caso em que essa técnica não se aplique. Obrigado!
AtomicBoolean
Isso funciona, também gosto das soluções de formato visual! Obrigado! Mas isso funciona para todas as versões iOS?
PaFi
4

Extensão de área segura para Objective-C

@implementation UIView (SafeArea)

- (NSLayoutAnchor *)safeTopAnchor{

    if (@available(iOS 11.0, *)){
        return self.safeAreaLayoutGuide.topAnchor;
    } else {
        return self.topAnchor;
    }

}


- (NSLayoutAnchor *)safeBottomAnchor{

    if (@available(iOS 11.0, *)) {
        return self.safeAreaLayoutGuide.bottomAnchor;
    } else {
        return self.bottomAnchor;
    }

}

@end
Pérola Negra
fonte
não funciona (Swift 4.2, iOS 12). O resultado ignora a barra de status
Vyachaslav Gerchicov
2

Swift 4.2 e 5.0. Suponha que você queira adicionar restrições à esquerda, à direita, superior e inferior ao viewBg. Então, você pode usar o código abaixo.

let guide = self.view.safeAreaLayoutGuide
viewBg.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
viewBg.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
viewBg.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
viewBg.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
Gurjinder Singh
fonte
1

Esta extensão ajuda a restringir um UIVIew à sua superview e superview + safeArea:

extension UIView {

    ///Constraints a view to its superview
    func constraintToSuperView() {
        guard let superview = superview else { return }
        translatesAutoresizingMaskIntoConstraints = false

        topAnchor.constraint(equalTo: superview.topAnchor).isActive = true
        leftAnchor.constraint(equalTo: superview.leftAnchor).isActive = true
        bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true
        rightAnchor.constraint(equalTo: superview.rightAnchor).isActive = true
    }

    ///Constraints a view to its superview safe area
    func constraintToSafeArea() {
        guard let superview = superview else { return }
        translatesAutoresizingMaskIntoConstraints = false

        topAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.topAnchor).isActive = true
        leftAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.leftAnchor).isActive = true
        bottomAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.bottomAnchor).isActive = true
        rightAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.rightAnchor).isActive = true
    }

}
Reimond Hill
fonte
0

Você pode usar view.safeAreaInsets conforme explicado aqui https://www.raywenderlich.com/174078/auto-layout-visual-format-language-tutorial-2

amostra de código (retirada de raywenderlich.com):

override func viewSafeAreaInsetsDidChange() {
  super.viewSafeAreaInsetsDidChange()

  if !allConstraints.isEmpty {
    NSLayoutConstraint.deactivate(allConstraints)
    allConstraints.removeAll()
  }

  let newInsets = view.safeAreaInsets
  let leftMargin = newInsets.left > 0 ? newInsets.left : Metrics.padding
  let rightMargin = newInsets.right > 0 ? newInsets.right : Metrics.padding
  let topMargin = newInsets.top > 0 ? newInsets.top : Metrics.padding
  let bottomMargin = newInsets.bottom > 0 ? newInsets.bottom : Metrics.padding

  let metrics = [
    "horizontalPadding": Metrics.padding,
    "iconImageViewWidth": Metrics.iconImageViewWidth,
    "topMargin": topMargin,
    "bottomMargin": bottomMargin,
    "leftMargin": leftMargin,
    "rightMargin": rightMargin]
}


let views: [String: Any] = [
  "iconImageView": iconImageView,
  "appNameLabel": appNameLabel,
  "skipButton": skipButton,
  "appImageView": appImageView,
  "welcomeLabel": welcomeLabel,
  "summaryLabel": summaryLabel,
  "pageControl": pageControl]

let iconVerticalConstraints = NSLayoutConstraint.constraints(
  withVisualFormat: "V:|-topMargin-[iconImageView(30)]",
  metrics: metrics,
  views: views)
allConstraints += iconVerticalConstraints

let topRowHorizontalFormat = """
  H:|-leftMargin-[iconImageView(iconImageViewWidth)]-[appNameLabel]-[skipButton]-rightMargin-|
  """
...
Serg
fonte