Come si utilizza il layout dell'area di sicurezza a livello di programmazione?


99

Dato che non uso gli storyboard per creare le mie visualizzazioni, mi chiedevo se esiste l'opzione "Usa guide per aree sicure" a livello di programmazione o qualcosa del genere.

Ho provato ad ancorare le mie opinioni a

view.safeAreaLayoutGuide

ma continuano a sovrapporsi al massimo nel simulatore di iPhone X.


1
Nessuna proprietà bool documentata su questo secondo: developer.apple.com/documentation/uikit/uiview/…
dvp.petrov

Che ne dici view.safeAreaInsets? Hai provato questo?
Karthikeyan Bose

@KarthikeyanBose sì, l'ho fatto senza fortuna, purtroppo.
Phillip

per me va bene. Che aspetto ha il codice
Daniel Springer il

Risposte:


155

Di seguito è riportato un codice di esempio (rif. Da: Guida al layout dell'area sicura ):
se si creano i vincoli nel codice, utilizzare la proprietà safeAreaLayoutGuide di UIView per ottenere gli ancoraggi del layout pertinenti. Ricreamo nel codice l'esempio precedente di Interface Builder per vedere come appare:

Supponendo di avere la vista verde come proprietà nel nostro controller di visualizzazione:

private let greenView = UIView()

Potremmo avere una funzione per impostare le visualizzazioni e i vincoli chiamati da viewDidLoad:

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

Crea i vincoli del margine iniziale e finale come sempre utilizzando layoutMarginsGuide della vista radice:

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

Ora, a meno che tu non stia prendendo di mira iOS 11 e versioni successive, dovrai racchiudere i vincoli della guida del layout dell'area sicura con #available e tornare alle guide del layout superiore e inferiore per le versioni precedenti di 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)
   ])
}

Risultato:

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine


Ecco la documentazione ufficiale per sviluppatori Apple per la guida al layout dell'area sicura


L'area sicura è necessaria per gestire il design dell'interfaccia utente per iPhone-X. Ecco le linee guida di base su Come progettare l'interfaccia utente per iPhone-X utilizzando il layout dell'area sicura


2
Sarebbe possibile dare anche questo nell'obiettivo-C? Sembra proprio quello che mi serve
Tom Hammond,

6
@TomHammond Qui è in Objective-C per te stackoverflow.com/a/47076040/5638630~~V~~singular~~2nd
Krunal

2
@ZonilyJame Ora sulla tua query - SaFeAreaLayout è un framework specifico per iOS (non specifico per dispositivo iPhoneX), sta sostituendo la guida al layout Top-Bottom in iOS 11, quindi dobbiamo usare / impostare le condizioni per iOS non per il dispositivo. SafeAreaLayout si prende cura dei design per tutti i tipi di dispositivi (iPhone-X e altri). Puoi chiedermi maggiori dettagli, se hai ancora domande / confusione.
Krunal

3
Eccezionale. Grazie :)
Rajamohan S

1
perché è accettata come risposta corretta? Ho provato a impostare una posizione concreta - il risultato è completamente casuale - il controllo è posizionato in una posizione imprevedibile!
Vyachaslav Gerchicov

82

In realtà sto usando un'estensione per questo e controllando se è iOS 11 o meno.

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
  }
}

È un modo così semplice per andare e fa il trucco. Grazie per l'idea.
alper_k

Questo è un modo così semplice ma carino per farlo! Nota l'uso di self.safeAreaLayoutGuideinvece di self.layoutMarginsGuide. Quello sicuro usato in questa risposta ha funzionato correttamente per me per rimanere nell'area sicura! Una cosa che suggerirei di cambiare sarebbe usare leadingAnchore trailingAnchorinvece di leftAnchore rightAnchor. Bravo!
Pranoy C

22

SafeAreaLayoutGuideè UIViewproprietà,

La parte superiore della safeAreaLayoutGuide indica il bordo superiore non oscurato della visualizzazione (ad esempio, non dietro la barra di stato o la barra di navigazione, se presente). Allo stesso modo per gli altri bordi.

Utilizzare safeAreaLayoutGuideper evitare che i nostri oggetti si ritagliano / si sovrappongono da angoli arrotondati, barre di navigazione, barre delle schede, barre degli strumenti e altre viste antenate.

Possiamo creare safeAreaLayoutGuideoggetti e impostare vincoli di oggetti rispettivamente.

I vincoli per Ritratto + Paesaggio sono -

Immagine ritratto

Immagine del paesaggio

        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


2
Mai e poi mai fare i vincoli di configurazione in viewDidAppear, a meno che tu non sappia assolutamente cosa stai facendo. viewDidAppearviene chiamato più volte e quindi i tuoi vincoli verranno duplicati ogni volta che viene chiamato.
Yevhen Dubinin

Sì! , Risposta modificata, puoi utilizzare come caso d'uso.
Jack

14

Per quelli di voi che usano SnapKit , proprio come me, la soluzione è ancorare i propri vincoli in questo view.safeAreaLayoutGuidemodo:

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()
     }
}

1
Bella risposta. Per renderlo un po 'più compatto potresti dire: if #available (iOS 11.0, *) {make.edges.equalTo (view.safeAreaLayoutGuide.snp.margins)}
Don Miguel

7

Sto usando questo invece di aggiungere vincoli di margine iniziale e finale 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]
                                          ]];

Controlla anche l'opzione per la versione inferiore di ios 11 dalla risposta di Krunal.


Assicurati di aggiungere già la tua vista a superView. È self.view Nel mio codice come un semplice esempio.
Tony TRAN

6

Usa 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]

3
Questa è la strada da percorrere se la tua vista non utilizza AutoLayout.
Jonathan Cabrera

4

Usa i vincoli con il formato visivo e ottieni il rispetto per l'area sicura 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))
    }
}

risultato


2
Si prega di fornire un commento durante la votazione negativa, in modo che possiamo tutti sapere se esiste un caso in cui questa tecnica non si applica. Grazie!
AtomicBoolean

Funziona, mi piacciono anche le soluzioni di formato visivo! Grazie! Ma funziona per tutte le versioni di iOS?
PaFi

4

Estensione area sicura Per 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

non funziona (Swift 4.2, iOS 12). Il risultato ignora la barra di stato
Vyachaslav Gerchicov

2

Swift 4.2 e 5.0. Supponiamo di voler aggiungere i vincoli Leading, Trailing, Top e Bottom su viewBg. Quindi, puoi usare il codice seguente.

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

1

Questa estensione ti aiuta a vincolare un UIVIew alla 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
    }

}

0

Puoi utilizzare view.safeAreaInsets come spiegato qui https://www.raywenderlich.com/174078/auto-layout-visual-format-language-tutorial-2

codice di esempio (tratto da 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-|
  """
...
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.