¿Cómo utilizo el diseño de área segura mediante programación?

99

Como no uso guiones gráficos para crear mis vistas, me preguntaba si existe la opción "Usar guías de áreas seguras" mediante programación o algo así.

Intenté anclar mis puntos de vista a

view.safeAreaLayoutGuide

pero siguen superponiendo la muesca superior en el simulador de iPhone X.

Phillip
fuente
1
No hay propiedad bool documentada sobre esto de acuerdo con: developer.apple.com/documentation/uikit/uiview/…
dvp.petrov
¿Qué tal view.safeAreaInsets? ¿Intentaste esto?
Karthikeyan Bose
@KarthikeyanBose sí lo hice sin suerte, lamentablemente.
Phillip
funciona para mi. ¿Cómo se ve el código
Daniel Springer

Respuestas:

155

Aquí hay un código de muestra (Ref de: Safe Area Layout Guide ):
Si crea sus restricciones en el código, use la propiedad safeAreaLayoutGuide de UIView para obtener los anclajes de diseño relevantes. Vamos a recrear el ejemplo anterior de Interface Builder en código para ver cómo se ve:

Suponiendo que tenemos la vista verde como una propiedad en nuestro controlador de vista:

private let greenView = UIView()

Podríamos tener una función para configurar las vistas y restricciones llamadas desde viewDidLoad:

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

Cree las restricciones de margen inicial y final como siempre utilizando la guía layoutMarginsGuide de la vista raíz:

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

Ahora, a menos que esté apuntando a iOS 11 y posterior, deberá ajustar las restricciones de la guía de diseño del área segura con #available y volver a las guías de diseño superior e inferior para versiones anteriores de 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:

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí


Aquí está la documentación oficial del desarrollador de Apple para la guía de diseño de áreas seguras


Se requiere Safe Area para manejar el diseño de la interfaz de usuario para iPhone-X. Aquí hay una guía básica sobre cómo diseñar una interfaz de usuario para iPhone-X usando el diseño de área segura

Krunal
fuente
2
¿Sería posible dar esto también en el objetivo C? Parece justo lo que necesito
Tom Hammond
6
@TomHammond Aquí está en Objective-C para usted stackoverflow.com/a/47076040/5638630
Krunal
2
@ZonilyJame Ahora sobre su consulta: SaFeAreaLayout es un marco específico de iOS (no específico del dispositivo iPhoneX), está reemplazando la guía de diseño superior-inferior en iOS 11, por lo tanto, debemos usar / establecer la condición para iOS, no para el dispositivo. SafeAreaLayout se encarga de los diseños para todo tipo de dispositivos (iPhone-X y otros). Puede pedirme más detalles, si aún tiene alguna duda / confusión.
Krunal
3
Increíble. Gracias :)
Rajamohan S
1
¿Por qué se acepta como respuesta correcta? Traté de configurar una posición concreta; el resultado es completamente aleatorio, ¡el control se coloca en una posición impredecible!
Vyachaslav Gerchicov
82

De hecho, estoy usando una extensión para él y controlando si es ios 11 o no.

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
fuente
Es una forma tan sencilla de hacerlo y funciona. Gracias por la idea.
alper_k
¡Esta es una manera tan simple pero agradable de hacerlo! Tenga en cuenta el uso de en self.safeAreaLayoutGuidelugar de self.layoutMarginsGuide. ¡El seguro usado en esta respuesta funcionó correctamente para que me mantuviera dentro del área segura! Una cosa que sugeriría cambiar sería usar leadingAnchory en trailingAnchorlugar de leftAnchory rightAnchor. ¡Bravo!
Pranoy C
22

SafeAreaLayoutGuidees UIViewpropiedad,

La parte superior de safeAreaLayoutGuide indica el borde superior despejado de la vista (por ejemplo, no detrás de la barra de estado o barra de navegación, si está presente). Lo mismo ocurre con los otros bordes.

Úselo safeAreaLayoutGuidepara evitar que nuestros objetos se recorten / superpongan desde esquinas redondeadas, barras de navegación, barras de pestañas, barras de herramientas y otras vistas anteriores.

Podemos crear safeAreaLayoutGuideobjetos y establecer restricciones de objetos respectivamente.

Las restricciones para retrato + paisaje son:

Imagen de retrato

Imagen de paisaje

        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
fuente
2
Nunca realice restricciones de configuración en el viewDidAppear, a menos que sepa absolutamente lo que está haciendo. viewDidAppearse llama varias veces y, por lo tanto, sus restricciones se duplicarán cada vez que se llame.
Yevhen Dubinin
¡Si! , Respuesta editada, puede usarla como caso de uso.
Jack
14

Para aquellos de ustedes que usan SnapKit , al igual que yo, la solución es anclar sus restricciones de esta view.safeAreaLayoutGuidemanera:

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
fuente
1
Gran respuesta. Para hacerlo un poco más compacto, podría decir: si #disponible (iOS 11.0, *) {make.edges.equalTo (view.safeAreaLayoutGuide.snp.margins)}
Don Miguel
7

Estoy usando esto en lugar de agregar restricciones de margen inicial y final al 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]
                                          ]];

También marque la opción para la versión inferior de ios 11 de la respuesta de Krunal.

Tony TRAN
fuente
Asegúrese de agregar yourView a superView. Es self.view en mi código como un ejemplo simple.
Tony TRAN
6

Uso UIWindowo 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
fuente
3
Este es el camino a seguir si su vista no usa AutoLayout.
Jonathan Cabrera
4

Use restricciones con formato visual y obtendrá respeto por el área segura de forma gratuita.

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

AtómicoBooleano
fuente
2
Proporcione un comentario cuando vote en contra, para que todos podamos saber si hay un caso en el que esta técnica no se aplica. ¡Gracias!
AtomicBoolean
Esto funciona, ¡también me gustan las soluciones de formato visual! ¡Gracias! Pero, ¿funciona esto para todas las versiones de iOS?
PaFi
4

Extensión 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
Perla Negra
fuente
no funciona (Swift 4.2, iOS 12). El resultado ignora la barra de estado
Vyachaslav Gerchicov
2

Swift 4.2 y 5.0. Suponga que desea agregar restricciones inicial, final, superior e inferior en viewBg. Entonces, puede usar el siguiente código.

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

Esta extensión le ayuda a restringir un UIVIew a su superview y 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
fuente
0

Puede utilizar view.safeAreaInsets como se explica aquí https://www.raywenderlich.com/174078/auto-layout-visual-format-language-tutorial-2

ejemplo de código (tomado 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
fuente