Uzyskaj bezpieczne wstawki na górnej i dolnej wysokości

178

Jaki byłby najwłaściwszy sposób na uzyskanie górnej i dolnej wysokości w niebezpiecznych obszarach na nowym iPhonie X?

wprowadź opis obrazu tutaj

Tulleb
źródło

Odpowiedzi:

365

Spróbuj tego :

W celu C

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

W Swift

if #available(iOS 11.0, *) {
    let window = UIApplication.shared.keyWindow
    let topPadding = window?.safeAreaInsets.top
    let bottomPadding = window?.safeAreaInsets.bottom
}
user6788419
źródło
1
@KrutikaSonawala bottomPadding + 10 (twoja wartość)
user6788419
12
Wydaje mi się, że to nie działa - wypisanie wartości UIApplication.shared.keyWindow? .SafeAreaInsets zwraca wszystkie 0. Jakieś pomysły?
Hai
2
Zrobiłem, mogę ograniczyć safeAreaLayoutGuides dowolnego widoku, ale bezpośrednio drukując wartość safeAreaInsets okna klucza po prostu zwraca zero rect. Jest to problematyczne, ponieważ muszę ręcznie ograniczyć widok, który został dodany do okna klucza, ale nie znajduje się w hierarchii widoku głównego kontrolera widoku.
Hai
6
Dla mnie keyWindowzawsze było niltak, że zmieniłem to na windows[0]i usunąłem ?z opcjonalnego łańcucha, a potem zadziałało.
George_E
2
Działa tylko wtedy, gdy pojawił się już widok kontrolera.
Wania
84

Aby uzyskać wysokość między prowadnicami układu, po prostu zrób

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

Więc full frame height = 812.0 ,safe area height = 734.0

Poniżej znajduje się przykład, w którym zielony widok ma ramkę guide.layoutFrame

wprowadź opis obrazu tutaj

Ladislav
źródło
Dzięki, to był trop, o którym też myślałem. Myślę, że nie ma bardziej niezawodnego rozwiązania. Ale dzięki temu otrzymuję tylko pełną niebezpieczną wysokość, prawda? Nie dwie wysokości dla każdego obszaru?
Tulleb
2
Po przedstawieniu widoków otrzymasz poprawną odpowiedź
Ladislav
2
Właściwie odpowiedź @ user6788419 też wygląda ładnie i znacznie krócej.
Tulleb
Otrzymuję wysokość 812.0 z tym kodem, używając widoku kontrolera. Więc używam UIApplication.sharedApplication.keyWindow.safeAreaLayoutGuide.layoutFrame, który ma bezpieczną ramkę.
Ferran Maylinch
1
@FerranMaylinch Nie jestem pewien, gdzie sprawdzasz wysokość, ale miałem też 812, co okazało się spowodowane sprawdzeniem wartości, zanim widok był widoczny. W takim przypadku wysokości będą takie same. "Jeśli widok nie jest obecnie zainstalowany w hierarchii widoków lub nie jest jeszcze widoczny na ekranie, krawędzie prowadnic układu są równe krawędziom widoku" developer.apple.com/documentation/ uikit / uiview /…
Nicholas Harlen
81

Swift 4, 5

Przypięcie widoku do zakotwiczenia obszaru bezpiecznego przy użyciu ograniczeń można wykonać w dowolnym miejscu cyklu życia kontrolera widoku, ponieważ są one kolejkowane przez interfejs API i obsługiwane po załadowaniu widoku do pamięci. Jednak uzyskanie wartości obszaru bezpiecznego wymaga czekania pod koniec cyklu życia kontrolera widoku, na przykład viewDidLayoutSubviews().

To podłącza się do dowolnego kontrolera widoku:

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
}

Wolę tę metodę niż wyrzucenie jej z okna (jeśli to możliwe), ponieważ tak zaprojektowano interfejs API, a co ważniejsze, wartości są aktualizowane podczas wszystkich zmian widoku, takich jak zmiany orientacji urządzenia.

Jednak niektóre niestandardowe kontrolery widoku prezentowanego nie mogą korzystać z powyższej metody (podejrzewam, że są w przejściowych widokach kontenerów). W takich przypadkach można pobrać wartości z głównego kontrolera widoku, który zawsze będzie dostępny w dowolnym miejscu w cyklu życia bieżącego kontrolera widoku.

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
źródło
3
Zadeklaruj, używając zamiast tego let topSafeArea: CGFloat!
Gusutafu
unikaj używania viewDidLayoutSubviews(które można wywołać wielokrotnie), oto rozwiązanie, które zadziała nawet na viewDidLoad: stackoverflow.com/a/53864017/7767664
user924
@ user924, to wartości nie zostaną zaktualizowane, gdy zmienią się bezpieczne obszary (tj. pasek stanu o podwójnej wysokości)
bsod
@bsod to będzie lepszy stackoverflow.com/a/3420550/7767664 (ponieważ dotyczy tylko paska stanu, a nie innych widoków)
user924
Lub zamiast przechodzić przez delegata aplikacji, możesz po prostu użyć bezpiecznego interfejsu API Apple wbudowanego w kontroler widoku.
bsod
21

Żadna z innych odpowiedzi tutaj nie zadziałała, ale tak się stało.

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
źródło
1
Kod będzie działał w dowolnej części cyklu życia kontrolera widoku. Jeśli chcesz używać view.safeAreaInsets.top, musisz upewnić się, że wywołujesz go po in viewDidAppear lub później. W viewDidLoad nie otrzymasz pliku view.safeAreaInsets.top z poprawną wartością, ponieważ nie został jeszcze zainicjowany.
user3204765
1
jak dotąd najlepsza odpowiedź
Rikco
17

Wszystkie odpowiedzi tutaj są pomocne. Dziękujemy wszystkim, którzy zaoferowali pomoc.

Jednak, jak widzę, ten temat dotyczący bezpiecznego obszaru jest nieco zagmatwany, co nie wydaje się być dobrze udokumentowane.

Więc podsumuję to tutaj jak najdokładniej, aby ułatwić zrozumienie safeAreaInsets, safeAreaLayoutGuidei LayoutGuide.

W iOS 7 firma Apple wprowadziła właściwości topLayoutGuidei bottomLayoutGuidew. UIViewControllerPozwoliły one na tworzenie ograniczeń, aby zawartość nie była ukrywana przez paski UIKit, takie jak pasek stanu, nawigacja lub pasek kart. Dzięki tym przewodnikom po układzie można było określić ograniczenia zawartości, unikając jej być ukryte przez górne lub dolne elementy nawigacyjne (paski UIKit, pasek stanu, pasek nawigacji lub pasek kart…).

Na przykład, jeśli chcesz, aby tableView zaczynał się od górnego ekranu, zrobiłeś coś takiego:

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

W iOS 11 firma Apple wycofała te właściwości, zastępując je przewodnikiem po układzie pojedynczego bezpiecznego obszaru

Obszar bezpieczny według Apple

Bezpieczne obszary pomagają umieścić widoki w widocznej części całego interfejsu. Kontrolery widoków zdefiniowane przez UIKit mogą umieszczać specjalne widoki na twoich treściach. Na przykład kontroler nawigacji wyświetla pasek nawigacji u góry zawartości bazowego kontrolera widoku. Nawet jeśli takie widoki są częściowo przezroczyste, nadal zasłaniają treść, która się pod nimi znajduje. W systemie tvOS obszar bezpieczny zawiera również wstawki przeskanowane ekranu, które reprezentują obszar objęty ramką ekranu.

Poniżej bezpieczny obszar wyróżniony w iPhone'ach 8 i iPhone'ach X:

wprowadź opis obrazu tutaj

safeAreaLayoutGuideJest właściwościąUIView

Aby uzyskać wysokość safeAreaLayoutGuide:

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

To zwróci wysokość strzałki na twoim zdjęciu.

A co z uzyskaniem górnego „wycięcia” i dolnych wysokości wskaźników ekranu głównego?

Tutaj użyjemy safeAreaInsets

Bezpieczny obszar widoku odzwierciedla obszar nieobjęty przez paski nawigacji, paski kart, paski narzędzi i inne elementy nadrzędne, które zasłaniają widok kontrolera widoku. (W tvOS, bezpieczny obszar odzwierciedla obszar nie zakryty przez ramkę ekranu.) Bezpieczny obszar dla widoku uzyskuje się, stosując wstawki w tej właściwości do prostokąta granic widoku. Jeśli widok nie jest aktualnie zainstalowany w hierarchii widoków lub nie jest jeszcze widoczny na ekranie, wstawki krawędzi w tej właściwości mają wartość 0.

Poniższy rysunek przedstawia niebezpieczny obszar i odległość od krawędzi w telefonie iPhone 8 i iPhone X-Series.

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj

Teraz, jeśli dodano pasek nawigacji

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj

Jak więc teraz uzyskać wysokość obszaru niebezpiecznego? użyjemysafeAreaInset

Oto rozwiązania, które jednak różnią się ważną rzeczą,

Pierwszy:

self.view.safeAreaInsets

To zwróci EdgeInsets, możesz teraz uzyskać dostęp do góry i dołu, aby poznać wstawki,

Drugi:

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

Pierwszy z nich jest wstawiany do widoku, więc jeśli istnieje pasek nawigacji, zostanie on uwzględniony, jednak drugi, do którego uzyskujesz dostęp do safeAreaInsets okna, więc pasek nawigacji nie będzie brany pod uwagę

mojtaba al moussawi
źródło
5

W iOS 11 istnieje metoda, która informuje, kiedy zmienił się safeArea.

override func viewSafeAreaInsetsDidChange() {
    super.viewSafeAreaInsetsDidChange()
    let top = view.safeAreaInsets.top
    let bottom = view.safeAreaInsets.bottom
}
Jaskółka oknówka
źródło
2

Pracuję z frameworkami CocoaPods i na wypadek, gdyby UIApplication.sharedbył niedostępny , używam safeAreaInsetsw widokach window:

if #available(iOS 11.0, *) {
    let insets = view.window?.safeAreaInsets
    let top = insets.top
    let bottom = insets.bottom
}
Tai Le
źródło
2

safeAreaLayoutGuide Gdy widok jest widoczny na ekranie, ten przewodnik odzwierciedla część widoku, która nie jest zakryta przez paski nawigacji, paski kart, paski narzędzi i inne widoki przodków. (W systemie tvOS bezpieczny obszar odzwierciedla obszar nie zakryty ramką ekranu). Jeśli widok nie jest obecnie zainstalowany w hierarchii widoków lub nie jest jeszcze widoczny na ekranie, krawędzie prowadnicy układu są równe krawędziom widoku.

Następnie, aby uzyskać wysokość czerwonej strzałki na zrzucie ekranu, jest to:

self.safeAreaLayoutGuide.layoutFrame.size.height
malhal
źródło
2

Swift 5, Xcode 11.4

`UIApplication.shared.keyWindow` 

Daje ostrzeżenie o wycofaniu. Opcja „keyWindow” została wycofana w iOS 13.0: nie powinna być używana dla aplikacji obsługujących wiele scen, ponieważ zwraca okno klucza we wszystkich połączonych scenach ”z powodu połączonych scen. Używam w ten sposób.

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
źródło
1

Objective-C Kto miał problem, gdy keyWindow jest równe nil . Po prostu umieść powyższy kod w viewDidAppear (nie w viewDidLoad )

DmitryShapkin
źródło
1
UIWindow *window = [[[UIApplication sharedApplication] delegate] window]; 
CGFloat fBottomPadding = window.safeAreaInsets.bottom;
Piotr
źródło
1

Rozszerzenie Swift 5

Może być używane jako rozszerzenie i wywoływane z: 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
    }
}

Rozszerzenie UIApplication jest opcjonalne, może być rozszerzeniem UIView lub czymkolwiek jest preferowane, lub prawdopodobnie nawet lepiej funkcją globalną.

Teodor
źródło
0

Bardziej zaokrąglone podejście

  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
źródło