Отримайте безпечну висоту верхньої та нижньої висоти


178

На новому iPhone X, який би був найбільш правильний спосіб отримати висоту і верхню, і нижню для небезпечних областей?

введіть тут опис зображення

Відповіді:


366

Спробуйте це :

В Цілі С

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

У Свіфта

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

1
@KrutikaSonawala bottomPadding + 10 (ваша цінність)
user6788419

12
Здається, це не працює для мене - роздруковуючи значення UIApplication.shared.keyWindow? .SafeAreaInsets повертає всі 0. Будь-які ідеї?
Хай

2
Так, я можу обмежитися безпекою будь-якого виду safeAreaLayoutGuides, але безпосередньо надрукувавши значення SafeAreaInsets ключового вікна просто повертає нульову пряму. Це проблематично, тому що мені потрібно вручну обмежити подання, яке було додано до ключового вікна, але не в ієрархії подання кореневого виду.
Хай

6
Для мене keyWindowзавжди було nilтак, що я змінив його windows[0]і вилучив ?із факультативного ланцюга, тоді він працював.
George_E

2
Він працює лише тоді, коли вигляд контролера вже з’явився.
Ваня

85

Щоб отримати висоту між направляючими компонування, які ви просто робите

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

Отже full frame height = 812.0,safe area height = 734.0

Нижче наводиться приклад, коли зелений вигляд має рамку guide.layoutFrame

введіть тут опис зображення


Дякую, що це був провід, про який я також думав. Я не думаю, що є більш надійне рішення. Але завдяки цьому я отримую лише повний небезпечний зріст, правда? Не дві висоти для кожної області?
Тюллеб

2
Як тільки погляди будуть викладені, це дасть вам правильну відповідь
Ладіслав

2
Насправді відповідь @ user6788419 також виглядає приємно і набагато коротше.
Тюллеб

Я отримую висоту 812,0 з цим кодом, використовуючи подання контролера. Тому я використовую UIApplication.sharedApplication.keyWindow.safeAreaLayoutGuide.layoutFrame, який має безпечний кадр.
Ферран Мейлінч

1
@FerranMaylinch Не знаю, де ти перевіряєш висоту, але у мене також був 812, який виявився через перевірку значення до того, як вид було видно. У такому випадку висоти будуть однаковими "Якщо подання наразі не встановлено в ієрархії перегляду або ще не видно на екрані, краї направляючих макетів рівні рівним краям подання" developer.apple.com/documentation/ uikit / uiview /…
Ніколас Харлен

81

Швидкий 4, 5

Закріпити погляд на якір безпечної зони за допомогою обмежень можна зробити в будь-якому місці життєвого циклу контролера перегляду, оскільки вони ставлять у чергу черговий інтерфейс API та обробляються після того, як подання завантажено в пам'ять. Однак для отримання значень безпечної зони потрібно чекати до кінця життєвого циклу контролера перегляду, наприкладviewDidLayoutSubviews() .

Це підключення до будь-якого контролера перегляду:

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
}

Я вважаю за краще цей метод видаляти його з вікна (коли це можливо), оскільки саме так було розроблено API і, що ще важливіше, значення оновлюються під час усіх змін перегляду, як-от зміни орієнтації пристрою.

Однак деякі представлені на замовлення контролери подання не можуть використовувати вищевказаний метод (я підозрюю, оскільки вони перебувають у перехідних режимах перегляду контейнерів). У таких випадках ви можете вимкнути значення контролера кореневого перегляду, які завжди будуть доступні в будь-якому місці життєвого циклу поточного контролера перегляду.

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
Будь ласка, заявіть, що замість цього використовуйте let topSafeArea: CGFloat!
Гусутафу

уникати використання viewDidLayoutSubviews(які можна назвати кілька разів), ось рішення , яке буде працювати навіть в viewDidLoad: stackoverflow.com/a/53864017/7767664
user924

@ user924, тоді значення не будуть оновлюватися, коли змінюються безпечні зони (тобто смуга стану подвійної висоти)
bsod

@bsod, тоді це буде краще stackoverflow.com/a/3420550/7767664 (тому що це лише для рядка стану, а не для інших переглядів)
user924

Або замість того, щоб переглядати делегата програми, ви можете просто використовувати безпечний інтерфейс API Apple, вбудований у контролер перегляду.
bsod

21

Жодна з інших відповідей тут не працювала для мене, але це було.

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
Ваш код буде працювати в будь-якій частині життєвого циклу контролера перегляду. Якщо ви хочете використовувати view.safeAreaInsets.top, вам потрібно переконатися, що ви будете викликати його після в viewDidAppear або пізніше. У viewDidLoad ви не отримаєте view.safeAreaInsets.top з правильним значенням, оскільки воно ще не ініціалізовано.
користувач3204765

1
найкраща відповідь поки що
Рікко

17

Усі відповіді тут корисні. Дякую всім, хто запропонував допомогу.

Однак, як я бачу, що тема безпечної зони трохи заплутана, що, здається, не буде добре зафіксовано.

Тому я підсумую це тут якомога більше кашлю, щоб зробити його легким для розуміння safeAreaInsets, safeAreaLayoutGuideіLayoutGuide .

В iOS 7 Apple представила topLayoutGuideі bottomLayoutGuideвластивості вUIViewController , вони дозволили вам створювати обмеження, щоб ваш вміст не приховувався барами UIKit, такими як статус, навігація або панель вкладок. За допомогою цих посібників для макета можна було вказати обмеження на вміст, уникаючи цього бути прихованими елементами навігації вгорі або внизу (смуги UIKit, рядок стану, навігація або панель вкладок…).

Так, наприклад, якщо ви хочете зробити tableView починається з верхнього екрану, ви зробили щось подібне:

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

В iOS 11 Apple втратила ці властивості, замінивши їх єдиним посібником щодо макета безпечної зони

Безпечна зона згідно Apple

Безпечні зони допомагають розмістити свої погляди у видимій частині загального інтерфейсу. Контролери перегляду, визначені UIKit, можуть розміщувати спеціальні представлення у верхній частині вашого вмісту. Наприклад, контролер навігації відображає панель навігації поверх вмісту базового контролера перегляду. Навіть коли такі погляди частково прозорі, вони все ще замикають вміст, що знаходиться під ними. У програмі tvOS безпечна зона також включає вкладені вкладиші екрану, які представляють область, покриту рамкою екрана.

Нижче, в iPhone 8 та iPhone X-серіях, виділено безпечне місце:

введіть тут опис зображення

Це safeAreaLayoutGuideвластивістьUIView

Щоб отримати висоту safeAreaLayoutGuide:

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

Це поверне висоту Стрілки на вашому знімку.

Тепер, а як же отримати високу "висоту" та нижню висоту індикатора домашнього екрана?

Тут ми будемо використовувати safeAreaInsets

Безпечна область перегляду відображає область, не охоплена навігаційними панелями, панелями вкладок, панелями інструментів та іншими предками, які затьмарюють подання контролера подання. (У tvOS безпечна зона відображає область, не охоплена рамкою екрана.) Ви отримуєте безпечну зону для перегляду, застосовуючи вставки в цій властивості до прямокутника меж перегляду. Якщо представлення наразі не встановлено в ієрархії подання або ще не видно на екрані, крайові вставки у цій властивості дорівнюють 0.

Далі буде показано небезпечну зону та відстань від країв на iPhone 8 та одному з iPhone X-Series.

введіть тут опис зображення

введіть тут опис зображення

Тепер, якщо додано панель навігації

введіть тут опис зображення

введіть тут опис зображення

Отже, тепер, як отримати високу небезпечну площу? ми будемо використовуватиsafeAreaInset

Ось рішення, однак вони важливо відрізняються,

Перший:

self.view.safeAreaInsets

Це поверне EdgeInsets, тепер ви можете отримати доступ до верху та знизу, щоб знати вставки,

Другий:

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

Перший ви берете вставки для перегляду, тому, якщо є панель навігації, він буде врахований, проте другий, який ви отримуєте доступ до SafeAreaInsets вікна, тому навігаційна панель не буде врахована


5

В iOS 11 є метод, який повідомляє, коли SafeArea змінилася.

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

2

Я працюю з рамками CocoaPods і в разі , якщо UIApplication.sharedце недоступно , то я використовую safeAreaInsetsв поле зору - х window:

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

2

safeAreaLayoutGuide Коли вигляд видно на екрані, цей посібник відображає частину подання, яка не охоплена навігаційними панелями, панелями вкладок, панелями інструментів та іншими видами предків. (У tvOS безпечна область відображає область, не охоплена рамкою екрана.) Якщо подання наразі не встановлено в ієрархії перегляду або ще не видно на екрані, краї направляючих макетів дорівнюють краям подання.

Потім, щоб отримати висоту червоної стрілки на скріншоті, це:

self.safeAreaLayoutGuide.layoutFrame.size.height

2

Швидкий 5, Xcode 11.4

`UIApplication.shared.keyWindow` 

Це дасть попередження про депресію. '' keyWindow '' застаріло в iOS 13.0: не слід використовувати для додатків, які підтримують кілька сцен, оскільки він повертає ключове вікно для всіх підключених сцен 'через пов’язані сцени. Я використовую цей спосіб.

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 Хто мав проблему, коли keyWindow дорівнює нулю . Просто поставте код вище в viewDidAppear (не в viewDidLoad)


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

1

Розширення Swift 5

Це можна використовувати як розширення та викликати за допомогою: 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
    }
}

Розширення UIApplication є необов'язковим, може бути розширенням UIView або будь-яким іншим варіантом, або, можливо, навіть краще глобальною функцією.


0

Більш округлий підхід

  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
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.