Ответы:
Попробуй это :
В цели 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
}
keyWindow
было всегда, nil
поэтому я изменил его windows[0]
и удалил ?
из необязательной цепочки, тогда это сработало.
Чтобы получить высоту между направляющими макета, вы просто делаете
let guide = view.safeAreaLayoutGuide
let height = guide.layoutFrame.size.height
Итак full frame height = 812.0
,safe area height = 734.0
Ниже приведен пример, где зеленый вид имеет рамку guide.layoutFrame
UIApplication.sharedApplication.keyWindow.safeAreaLayoutGuide.layoutFrame
, который имеет безопасную рамку.
Свифт 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
}
viewDidLayoutSubviews
(которое можно вызывать несколько раз), вот решение, которое будет работать даже в viewDidLoad
: stackoverflow.com/a/53864017/7767664
Ни один из других ответов здесь не работал для меня, но это сработало.
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
}
Все ответы здесь полезны, спасибо всем, кто предложил помощь.
Однако, поскольку я вижу, что тема безопасной области немного запутана, что не будет хорошо документировано.
Поэтому я кратко изложу это здесь, чтобы облегчить понимание 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 окна, поэтому панель навигации не будет рассматриваться.
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 })
}
}
safeAreaLayoutGuide Когда представление отображается на экране, это руководство отражает часть представления, которая не покрыта панелями навигации, панелями вкладок, панелями инструментов и другими представлениями предков. (В tvOS безопасная область отражает область, не покрытую рамкой экрана.) Если вид в настоящее время не установлен в иерархии видов или еще не виден на экране, края направляющих макета равны краям вида.
Затем, чтобы получить высоту красной стрелки на скриншоте это:
self.safeAreaLayoutGuide.layoutFrame.size.height
Objective-C У кого была проблема, когда keyWindow равно nil . Просто поместите код выше в viewDidAppear (не в viewDidLoad)
Расширение 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 или любым другим, предпочтительным, или, возможно, даже лучше глобальной функцией.
Более округленный подход
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
}
}
Для тех из вас, кто переходит в ландшафтный режим, вы должны обязательно использовать 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
}
}