Получить безопасную область вставки верхней и нижней высоты

178

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

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

Tulleb
источник

Ответы:

367

Попробуй это :

В цели C

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
}
user6788419
источник
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
После того, как мнения были изложены, это даст вам правильный ответ
Ladislav
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
}
BSOD
источник
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
  }
ScottyBlades
источник
1
Ваш код будет работать в любой части жизненного цикла контроллера представления. Если вы хотите использовать view.safeAreaInsets.top, то вам нужно убедиться, что вы вызываете его после в viewDidAppear или позже. В viewDidLoad вы не получите view.safeAreaInsets.top с правильным значением, поскольку оно еще не инициализировано.
user3204765
1
лучший ответ на данный момент
Рикко
17

Все ответы здесь полезны, спасибо всем, кто предложил помощь.

Однако, поскольку я вижу, что тема безопасной области немного запутана, что не будет хорошо документировано.

Поэтому я кратко изложу это здесь, чтобы облегчить понимание safeAreaInsets, safeAreaLayoutGuideи LayoutGuide.

В прошивке 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
}
Мартин
источник
3

Swift 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 })
    }
}
user1039695
источник
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
malhal
источник
1

Objective-C У кого была проблема, когда keyWindow равно nil . Просто поместите код выше в viewDidAppear (не в viewDidLoad)

DmitryShapkin
источник
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
    }
}
johndpope
источник
0

Для тех из вас, кто переходит в ландшафтный режим, вы должны обязательно использовать viewSafeAreaInsetsDidChange после поворота, чтобы получить наиболее обновленные значения:

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
        }
}
Оз Шабат
источник