Ottieni aree di sicurezza inserto in alto e in basso


178

Sul nuovo iPhone X, quale sarebbe il modo più corretto per ottenere sia l'altezza superiore che inferiore per le aree non sicure?

inserisci qui la descrizione dell'immagine

Risposte:


367

Prova questo :

In Objective C

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

In Swift

if #available(iOS 11.0, *) {
    let window = UIApplication.shared.keyWindow
    let topPadding = window?.safeAreaInsets.top
    let bottomPadding = window?.safeAreaInsets.bottom
}

1
@KrutikaSonawala bottomPadding + 10 (your value)
user6788419

12
Questo non sembra funzionare per me: stampare il valore di UIApplication.shared.keyWindow? .SafeAreaInsets restituisce tutti gli 0. Qualche idea?
Hai,

2
Ho fatto, posso vincolare contro safeAreaLayoutGuides di qualsiasi vista ma stampare direttamente il valore di safeAreaInsets della finestra chiave restituisce solo un valore zero. Questo è problematico perché ho bisogno di vincolare manualmente una vista che è stata aggiunta alla finestra della chiave ma non è nella gerarchia delle viste del controller della vista radice.
Hai,

6
Per me, è keyWindowstato sempre nilcosì l'ho cambiato windows[0]e rimosso ?dal concatenamento opzionale, quindi ha funzionato.
George_E,

2
Funziona solo quando è già apparsa la vista di un controller.
Vanya,

85

Per ottenere l'altezza tra le guide di layout è sufficiente

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

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

Di seguito è riportato l'esempio in cui la vista verde ha una cornice di guide.layoutFrame

inserisci qui la descrizione dell'immagine


Grazie è stato il vantaggio a cui stavo anche pensando. Non penso che ci sia una soluzione più affidabile. Ma con questo ottengo solo l'intera altezza non sicura, giusto? Non due altezze per ciascuna area?
Tulleb,

2
Una volta che le opinioni sono state presentate, questo ti darà la risposta corretta
Ladislav,

2
In realtà anche la risposta di @utente6788419 sembra carina e molto più breve.
Tulleb,

Sto ottenendo un'altezza di 812.0 con quel codice, usando la vista di un controller. Quindi sto usando UIApplication.sharedApplication.keyWindow.safeAreaLayoutGuide.layoutFrame, che ha il frame sicuro.
Ferran Maylinch,

1
@FerranMaylinch Non sono sicuro di dove stai controllando l'altezza, ma ho anche avuto 812, che si è rivelato essere dovuto al controllo del valore prima che la vista fosse visibile. In tal caso le altezze saranno le stesse "Se la vista non è attualmente installata in una gerarchia di viste o non è ancora visibile sullo schermo, i bordi della guida di layout sono uguali ai bordi della vista" developer.apple.com/documentation/ uikit / uiview / ...
Nicholas Harlen,

81

Swift 4, 5

Per bloccare una vista su un ancoraggio di area sicura, è possibile utilizzare i vincoli in qualsiasi punto del ciclo di vita del controller della vista perché sono accodati dall'API e gestiti dopo che la vista è stata caricata in memoria. Tuttavia, ottenere valori di aree sicure richiede l'attesa verso la fine del ciclo di vita di un controller di visualizzazione, ad esempio viewDidLayoutSubviews().

Questo si collega a qualsiasi controller di visualizzazione:

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
}

Preferisco questo metodo per toglierlo dalla finestra (quando possibile) perché è come è stata progettata l'API e, soprattutto, i valori vengono aggiornati durante tutte le modifiche alla vista, come le modifiche all'orientamento del dispositivo.

Tuttavia, alcuni controller di visualizzazione personalizzati non possono utilizzare il metodo sopra (sospetto perché si trovano in viste contenitore temporanee). In tali casi, è possibile ottenere i valori dal controller della vista radice, che sarà sempre disponibile in qualsiasi punto del ciclo di vita del controller della vista corrente.

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
}

3
Si prega di dichiarare utilizzando let topSafeArea: CGFloat invece!
Gusutafu,

evitare l'uso viewDidLayoutSubviews(che può essere chiamato più volte), ecco una soluzione che funzionerà anche in viewDidLoad: stackoverflow.com/a/53864017/7767664
user924

@utente924 quindi i valori non verranno aggiornati quando cambiano le aree sicure (cioè la barra di stato a doppia altezza)
bsod

@bsod, allora sarà meglio stackoverflow.com/a/3420550/7767664 (perché è solo per la barra di stato e non per altre visualizzazioni)
user924

Oppure, invece di passare attraverso il delegato dell'app, puoi semplicemente utilizzare l'API dell'area sicura Apple integrata nel controller di visualizzazione.
bsod,

21

Nessuna delle altre risposte qui ha funzionato per me, ma questo ha funzionato.

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
  }

1
Il codice funzionerà in qualsiasi parte del ciclo di vita del controller di visualizzazione. Se vuoi usare view.safeAreaInsets.top, devi assicurarti di chiamarlo dopo in viewDidAppear o successivo. In viewDidLoad, non otterrai il view.safeAreaInsets.top con il valore corretto perché non è stato ancora inizializzato.
user3204765

1
migliore risposta finora
Rikco

17

Tutte le risposte qui sono utili, grazie a tutti coloro che hanno offerto aiuto.

Tuttavia, poiché vedo che l'argomento relativo all'area di sicurezza è un po 'confuso, il che non sembra essere ben documentato.

Quindi mi riassumere qui come poltiglia possibile per rendere più facile da capire safeAreaInsets, safeAreaLayoutGuidee LayoutGuide.

In iOS 7, Apple ha introdotto le proprietà topLayoutGuidee , Ti hanno permesso di creare vincoli per impedire che i tuoi contenuti venissero nascosti dalle barre UIKit come lo stato, la navigazione o la barra delle schede Era possibile con queste guide di layout specificare i vincoli sui contenuti, evitandoli essere nascosto dagli elementi di navigazione superiore o inferiore (barre UIKit, barra di stato, barra di navigazione o barra delle schede ...).bottomLayoutGuideUIViewController

Quindi, ad esempio, se vuoi creare un tableView inizia dalla schermata principale, hai fatto qualcosa del genere:

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

In iOS 11 Apple ha deprecato queste proprietà sostituendole con una singola guida al layout dell'area sicura

Area sicura secondo Apple

Le aree sicure consentono di posizionare le viste all'interno della parte visibile dell'interfaccia generale. I controller di visualizzazione definiti da UIKit possono posizionare viste speciali in cima ai tuoi contenuti. Ad esempio, un controller di navigazione visualizza una barra di navigazione in cima al contenuto del controller di visualizzazione sottostante. Anche quando tali viste sono parzialmente trasparenti, occludono comunque il contenuto sottostante. In tvOS, l'area sicura include anche inserti overscan dello schermo, che rappresentano l'area coperta dalla cornice dello schermo.

Di seguito, un'area sicura evidenziata in iPhone 8 e iPhone serie X:

inserisci qui la descrizione dell'immagine

Il safeAreaLayoutGuideè una struttura diUIView

Per ottenere l'altezza di safeAreaLayoutGuide:

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

Ciò restituirà l'altezza della freccia nella tua foto.

Ora, che ne dici di ottenere le altezze di "tacca" e di fondo della schermata principale in alto?

Qui useremo il safeAreaInsets

L'area sicura di una vista riflette l'area non coperta da barre di navigazione, barre delle schede, barre degli strumenti e altri antenati che oscurano la vista di un controller di vista. (In tvOS, l'area sicura riflette l'area non coperta dalla cornice dello schermo.) Si ottiene l'area sicura per una vista applicando gli inserti in questa proprietà al rettangolo dei limiti della vista. Se la vista non è attualmente installata in una gerarchia di viste o non è ancora visibile sullo schermo, gli inserti dei bordi in questa proprietà sono 0.

Quanto segue mostrerà l'area non sicura e la distanza dai bordi su iPhone 8 e uno di iPhone X-Series.

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

Ora, se aggiunta la barra di navigazione

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

Quindi, ora come ottenere l'altezza dell'area non sicura? useremo ilsafeAreaInset

Ecco le soluzioni, tuttavia differiscono in una cosa importante,

Il primo:

self.view.safeAreaInsets

Ciò restituirà EdgeInsets, ora puoi accedere in alto e in basso per conoscere gli inserti,

Il secondo:

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

Il primo a cui stai prendendo gli inserti di visualizzazione, quindi se viene considerata una barra di navigazione, verrà presa in considerazione la seconda, mentre stai accedendo a safeAreaInsets della finestra, quindi la barra di navigazione non verrà considerata


5

In iOS 11 c'è un metodo che dice quando è cambiato SafeArea.

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

2

Sto lavorando con i framework CocoaPods e nel caso in cui nonUIApplication.shared sia disponibile, allora uso safeAreaInsetsin view window:

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

2

safeAreaLayoutGuide Quando la vista è visibile sullo schermo, questa guida riflette la parte della vista che non è coperta da barre di navigazione, barre delle schede, barre degli strumenti e altre viste degli antenati. (In tvOS, l'area di sicurezza riflette l'area non coperta dalla cornice dello schermo.) Se la vista non è attualmente installata in una gerarchia di viste o non è ancora visibile sullo schermo, i bordi della guida del layout sono uguali ai bordi della vista.

Quindi per ottenere l'altezza della freccia rossa nello screenshot è:

self.safeAreaLayoutGuide.layoutFrame.size.height

2

Swift 5, Xcode 11.4

`UIApplication.shared.keyWindow` 

Darà un avviso di deprecazione. '' keyWindow 'è stato deprecato in iOS 13.0: non deve essere utilizzato per applicazioni che supportano più scene poiché restituisce una finestra chiave in tutte le scene connesse a causa delle scene connesse. Uso così.

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

1

Objective-C Chi ha avuto il problema quando keyWindow è uguale a zero . Basta inserire il codice sopra in viewDidAppear (non in viewDidLoad)


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

1

Estensione Swift 5

Può essere usato come estensione e chiamato con: 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'estensione di UIApplication è facoltativa, può essere un'estensione di UIView o qualunque cosa sia preferita, o probabilmente anche meglio una funzione globale.


0

Un approccio più arrotondato

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

0

Per quelli di voi che cambiano in modalità orizzontale, è necessario assicurarsi di utilizzare viewSafeAreaInsetsDidChange dopo la rotazione per ottenere i valori più aggiornati:

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