Holen Sie sich einen sicheren Bereich in oberen und unteren Höhen

177

Was wäre auf dem neuen iPhone X der beste Weg, um sowohl die obere als auch die untere Höhe für die unsicheren Bereiche zu ermitteln?

Geben Sie hier die Bildbeschreibung ein

Tulleb
quelle

Antworten:

364

Versuche dies :

In Ziel 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
}
user6788419
quelle
1
@ KrutikaSonawala bottomPadding + 10 (Ihr Wert)
user6788419
12
Dies scheint für mich nicht zu funktionieren - das Ausdrucken des Werts von UIApplication.shared.keyWindow? .SafeAreaInsets gibt alle Nullen zurück. Irgendwelche Ideen?
Hai
2
Ich kann mich gegen safeAreaLayoutGuides jeder Ansicht beschränken, aber wenn ich den Wert der safeAreaInsets des Schlüsselfensters direkt ausdrucke, wird nur eine Null-Rechteck zurückgegeben. Dies ist problematisch, da ich eine Ansicht, die dem Schlüsselfenster hinzugefügt wurde, aber nicht in der Ansichtshierarchie des Stammansichts-Controllers enthalten ist, manuell einschränken muss.
Hai
5
Für mich keyWindowwar es immer nilso, dass ich es geändert windows[0]und ?aus der optionalen Verkettung entfernt habe, dann hat es funktioniert.
George_E
2
Es funktioniert nur, wenn die Ansicht eines Controllers bereits angezeigt wurde.
Wanja
84

Um die Höhe zwischen den Layout-Anleitungen zu ermitteln, müssen Sie dies nur tun

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

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

Unten sehen Sie das Beispiel, in dem die grüne Ansicht einen Rahmen von hat guide.layoutFrame

Geben Sie hier die Bildbeschreibung ein

Ladislav
quelle
Danke, das war der Hinweis, über den ich auch nachgedacht habe. Ich glaube nicht, dass es eine zuverlässigere Lösung gibt. Aber damit bekomme ich nur die volle unsichere Höhe, oder? Nicht zwei Höhen für jeden Bereich?
Tulleb
2
Sobald die Ansichten festgelegt wurden, erhalten Sie die richtige Antwort
Ladislav
2
Eigentlich sieht die Antwort von @ user6788419 auch gut aus und ist viel kürzer.
Tulleb
Ich erhalte mit diesem Code eine Höhe von 812,0, wenn ich die Ansicht eines Controllers verwende. Also benutze ich UIApplication.sharedApplication.keyWindow.safeAreaLayoutGuide.layoutFrame, was den sicheren Rahmen hat.
Ferran Maylinch
1
@FerranMaylinch Ich bin mir nicht sicher, wo Sie die Höhe überprüfen, aber ich hatte auch 812, was darauf zurückzuführen war, dass der Wert überprüft wurde, bevor die Ansicht sichtbar war. In diesem Fall sind die Höhen gleich. "Wenn die Ansicht derzeit nicht in einer Ansichtshierarchie installiert ist oder noch nicht auf dem Bildschirm angezeigt wird, entsprechen die Kanten der Layoutführung den Kanten der Ansicht." Developer.apple.com/documentation/ uikit / uiview /…
Nicholas Harlen
80

Swift 4, 5

Das Anheften einer Ansicht an einen sicheren Bereichsanker mithilfe von Einschränkungen kann an einer beliebigen Stelle im Lebenszyklus des Ansichtscontrollers erfolgen, da diese von der API in die Warteschlange gestellt und nach dem Laden der Ansicht in den Speicher verarbeitet werden. Das Abrufen von Sicherheitsbereichswerten erfordert jedoch das Warten gegen Ende des Lebenszyklus eines View Controllers, zviewDidLayoutSubviews() .

Dies wird an jeden View Controller angeschlossen:

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
}

Ich bevorzuge diese Methode, um sie aus dem Fenster zu entfernen (wenn möglich), da die API so entworfen wurde und, was noch wichtiger ist, die Werte bei allen Ansichtsänderungen aktualisiert werden, z. B. bei Änderungen der Geräteorientierung.

Einige benutzerdefinierte Ansichtssteuerungen können die oben beschriebene Methode jedoch nicht verwenden (ich vermute, weil sie sich in vorübergehenden Containeransichten befinden). In solchen Fällen können Sie die Werte vom Root-View-Controller abrufen, der immer überall im Lebenszyklus des aktuellen View-Controllers verfügbar ist.

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
quelle
3
Bitte deklarieren Sie stattdessen let topSafeArea: CGFloat!
Gusutafu
Vermeiden Sie die Verwendung viewDidLayoutSubviews(die mehrfach aufgerufen werden kann). Hier ist eine Lösung, die auch in viewDidLoadfolgenden Fällen
funktioniert
@ user924 dann werden die Werte nicht aktualisiert, wenn sich die sicheren Bereiche ändern (dh die Statusleiste mit doppelter Höhe)
bsod
@bsod dann ist dies besser stackoverflow.com/a/3420550/7767664 (weil es nur für die Statusleiste und nicht für andere Ansichten ist)
user924
Oder anstatt den App-Delegaten zu durchlaufen, können Sie einfach die in den View-Controller integrierte Safe-Area-API von Apple verwenden.
bsod
21

Keine der anderen Antworten hier hat für mich funktioniert, aber das hat funktioniert.

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
quelle
1
Ihr Code funktioniert in jedem Teil des Lebenszyklus des View Controllers. Wenn Sie view.safeAreaInsets.top verwenden möchten, müssen Sie sicherstellen, dass Sie es in viewDidAppear oder höher aufrufen. In viewDidLoad erhalten Sie die Datei view.safeAreaInsets.top nicht mit dem richtigen Wert, da sie noch nicht initialisiert ist.
user3204765
1
beste Antwort bisher
Rikco
17

Alle Antworten hier sind hilfreich. Vielen Dank an alle, die Hilfe angeboten haben.

Wie ich jedoch sehe, ist das Thema des sicheren Bereichs etwas verwirrt, was nicht gut dokumentiert zu sein scheint.

Deshalb werde ich es hier so gut wie möglich zusammenfassen, um es leicht verständlich zu machen safeAreaInsets, safeAreaLayoutGuideund LayoutGuide.

In iOS 7 führte Apple die Eigenschaften topLayoutGuideund bottomLayoutGuidein einUIViewController . Sie ermöglichten es Ihnen, Einschränkungen zu erstellen, um zu verhindern, dass Ihre Inhalte durch UIKit-Leisten wie Status, Navigation oder Registerkartenleiste ausgeblendet werden. Mit diesen Layout-Anleitungen konnten Sie Einschränkungen für den Inhalt festlegen und diese vermeiden durch obere oder untere Navigationselemente (UIKit-Leisten, Statusleiste, Navigations- oder Registerkartenleiste…) ausgeblendet werden.

Wenn Sie beispielsweise eine Tabellenansicht erstellen möchten, die vom oberen Bildschirm aus beginnt, haben Sie Folgendes getan:

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

In iOS 11 hat Apple diese Eigenschaften abgelehnt und sie durch eine einzige Anleitung zum Layout sicherer Bereiche ersetzt

Sicherer Bereich laut Apple

Mithilfe sicherer Bereiche können Sie Ihre Ansichten im sichtbaren Bereich der gesamten Benutzeroberfläche platzieren. UIKit-definierte Ansichts-Controller können spezielle Ansichten über Ihren Inhalten platzieren. Ein Navigationscontroller zeigt beispielsweise eine Navigationsleiste über dem Inhalt des zugrunde liegenden Ansichtscontrollers an. Selbst wenn solche Ansichten teilweise transparent sind, verschließen sie den darunter liegenden Inhalt. In tvOS enthält der sichere Bereich auch die Overscan-Einsätze des Bildschirms, die den Bereich darstellen, der von der Bildschirmblende abgedeckt wird.

Unten ein sicherer Bereich, der in iPhone 8 und iPhone X-Serie hervorgehoben wird:

Geben Sie hier die Bildbeschreibung ein

Das safeAreaLayoutGuide ist eine Eigenschaft vonUIView

So erhalten Sie die Höhe von safeAreaLayoutGuide:

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

Dadurch wird die Höhe des Pfeils in Ihrem Bild zurückgegeben.

Was ist nun mit der Anzeige der oberen "Kerbe" und der unteren Höhe des Startbildschirms?

Hier werden wir die verwenden safeAreaInsets

Der sichere Bereich einer Ansicht spiegelt den Bereich wider, der nicht von Navigationsleisten, Registerkartenleisten, Symbolleisten und anderen Vorfahren abgedeckt wird, die die Ansicht eines Ansichtscontrollers verdecken. (In tvOS spiegelt der sichere Bereich den Bereich wider, der nicht von der Bildschirmblende abgedeckt wird.) Sie erhalten den sicheren Bereich für eine Ansicht, indem Sie die Einfügungen in dieser Eigenschaft auf das Begrenzungsrechteck der Ansicht anwenden. Wenn die Ansicht derzeit nicht in einer Ansichtshierarchie installiert ist oder noch nicht auf dem Bildschirm angezeigt wird, sind die Kanteneinfügungen in dieser Eigenschaft 0.

Im Folgenden werden der unsichere Bereich und der Abstand zu den Kanten des iPhone 8 und eines der iPhone X-Serien angezeigt.

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Nun, wenn Navigationsleiste hinzugefügt

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Wie kann man nun die Höhe des unsicheren Bereichs ermitteln? Wir werden die verwendensafeAreaInset

Hier sind Lösungen, die sich jedoch in einer wichtigen Sache unterscheiden:

Erster:

self.view.safeAreaInsets

Dadurch werden die EdgeInsets zurückgegeben. Sie können jetzt oben und unten darauf zugreifen, um die Insets zu kennen.

Das Zweite:

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

Beim ersten Mal nehmen Sie die Ansichtseinfügungen auf. Wenn also eine Navigationsleiste vorhanden ist, wird diese berücksichtigt. Beim zweiten Mal greifen Sie jedoch auf die safeAreaInsets des Fensters zu, sodass die Navigationsleiste nicht berücksichtigt wird

mojtaba al moussawi
quelle
5

In iOS 11 gibt es eine Methode, die angibt, wann sich safeArea geändert hat.

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

Ich arbeite mit CocoaPods Frameworks und im Falle UIApplication.sharedist nicht verfügbar , dann verwende ich safeAreaInsetsim Hinblick auf die window:

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

safeAreaLayoutGuide Wenn die Ansicht auf dem Bildschirm angezeigt wird, spiegelt diese Anleitung den Teil der Ansicht wider, der nicht von Navigationsleisten, Registerkartenleisten, Symbolleisten und anderen Vorfahrenansichten abgedeckt wird. (In tvOS spiegelt der sichere Bereich den Bereich wider, der die Bildschirmblende nicht abdeckt.) Wenn die Ansicht derzeit nicht in einer Ansichtshierarchie installiert ist oder auf dem Bildschirm noch nicht sichtbar ist, entsprechen die Kanten der Layoutführung den Kanten der Ansicht.

Um die Höhe des roten Pfeils im Screenshot zu ermitteln, gilt Folgendes:

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

Swift 5, Xcode 11.4

`UIApplication.shared.keyWindow` 

Es wird eine Abwertungswarnung ausgegeben. '' keyWindow '' war in iOS 13.0 veraltet: Sollte nicht für Anwendungen verwendet werden, die mehrere Szenen unterstützen, da aufgrund verbundener Szenen ein Schlüsselfenster für alle verbundenen Szenen zurückgegeben wird. Ich benutze diesen Weg.

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

Objective-C Wer hatte das Problem , wenn keyWindow gleich ist Null . Fügen Sie einfach den obigen Code in viewDidAppear ein (nicht in viewDidLoad).

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

Swift 5-Erweiterung

Dies kann als Erweiterung verwendet und aufgerufen werden mit: 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
    }
}

Die Erweiterung von UIApplication ist optional, kann eine Erweiterung von UIView sein oder was auch immer bevorzugt wird, oder wahrscheinlich sogar eine globale Funktion.

Theodor
quelle
0

Ein runderer Ansatz

  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
quelle