Руководство по макету безопасной области iOS 11, обратная совместимость
12 ответов
Мне удалось поработать с новыми руководствами по макету Безопасной области и поддерживать обратную совместимость с iOS 9 и iOS 10:(РЕДАКТИРОВАТЬ: как указано в комментариях @NickEntin, эта реализация предполагает, что присутствует строка состояния, которая не будет Будьте верны в ландшафте на iPhone X. В результате много места до верха (20 баллов). Однако он будет работать отлично.
Например, если вы хотите, чтобы изображение было на 10 пунктов ниже строки состояния (и на 10 пунктов ниже корпуса датчика на iPhone X):
- В вашем XIB перейдите к
File Inspector
и включить сейф, проверивUse Safe Area Layout Guides
, - Создайте ограничение от вершины представления к вершине основного представления, с
>=
(больше или равно) ограничение, постоянное30
(30, потому что мы хотим, чтобы интервал в 10 баллов к строке состояния был выше 20 баллов) и приоритетHigh
(750). - Создайте ограничение от вершины представления до вершины безопасной области, с
=
(равное) ограничение, постоянное10
и приоритетLow
(250).
То же самое можно сделать для вида внизу (и для движения вперед / назад или влево / вправо в безопасную зону):
- В вашем XIB перейдите к
File Inspector
и включить сейф, проверивUse Safe Area Layout Guides
, - Создайте ограничение от нижней части представления к основному основанию, с
>=
(больше или равно) ограничение, постоянное10
и приоритетHigh
(750). - Создайте ограничение от нижней части представления до нижней части безопасной области, с
=
(равное) ограничение, постоянное10
и приоритетLow
(250).
Обратная совместимость Safe Areas для iOS 9 и iOS 10 работает только при использовании раскадровок. Если вы используете xibs, не существует руководства по верстке. https://forums.developer.apple.com/thread/87329
Обходные пути, кажется, либо
(а) перенести свои xibs в раскадровки, или
(б) добавить некоторые дополнительные ограничения программно.
Если (а) на самом деле не вариант, ручной подход будет выглядеть примерно так:
Предполагая, что у вас есть представление в xib, которое вы хотите оставить в безопасной зоне (то есть ниже любой строки состояния или панели навигации).
Добавьте ограничения в xib между вашим представлением и безопасной областью для iOS 11. Присвойте верхнему ограничению приоритет 750.
В вашем контроллере представления добавьте свойство:
@property (nonatomic, strong) NSLayoutConstraint *topLayoutConstraint;
А затем в viewDidLayoutSubviews:
- (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; if (@available(iOS 11, *)) { // safe area constraints already set } else { if (!self.topLayoutConstraint) { self.topLayoutConstraint = [self.<yourview>.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor]; [self.topLayoutConstraint setActive:YES]; } } }
Новое ограничение будет создано только для iOS 9 и iOS 10, имеет приоритет по умолчанию 1000 и переопределяет приоритет в xib.
Повторите для нижнего ограничения, если вам нужно избежать индикатора дома.
Версия Swift 4:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 11, *) {
// safe area constraints already set
} else {
if topLayoutConstraint == nil {
topLayoutConstraint = <yourview>.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor)
topLayoutConstraint?.isActive = true
}
}
}
Определенно есть по крайней мере одна проблема обратной совместимости с ограничениями безопасной области iOS 11, которую я наблюдал в Xcode 9 GM- контроллеры push-представления с ограничениями безопасной области.
Если ваша навигационная панель скрыта, и вы выдвигаете представление верхней области безопасной области, то нажатое представление будет перекрывать строку состояния в iOS 9 и 10.
Если панель навигации видима и "под верхними панелями" отключена, то толкаемое представление все равно будет перемещаться вверх под панелью навигации, чтобы попасть в верхнюю часть экрана. Панель навигации размещена правильно.
На iOS 11 раскладка будет правильной в обоих случаях.
Вот простой пример: http://www.filedropper.com/foobar
И вот видео этого со скрытой панелью навигации (iOS 10.3 слева, iOS 11 справа): https://vimeo.com/234174841/1e27a96a87
Вот версия, в которой видна навигационная панель (включена в перо): https://vimeo.com/234316256/f022132d57
Я подал это как Радар #34477706.
Спасибо Sander за указание на видимый случай навигационной панели.
Если вы используете xib без раскадровки, то на ios 10 у них нет направляющих макетов. Поэтому переместите xib на раскадровку, чтобы обеспечить обратную совместимость.
Swift 5
Я просто делаю это. Это просто и очень похоже на настоящую вещь (просто добавлена буква "r").
extension UIView {
var saferAreaLayoutGuide: UILayoutGuide {
get {
if #available(iOS 11.0, *) {
return self.safeAreaLayoutGuide
} else {
return self.layoutMarginsGuide
}
}
}
}
Используйте так:
button.topAnchor.constraint(equalTo: view.saferAreaLayoutGuide.topAnchor, constant: 16)
Да, ваш проект / приложение будет работать в версиях iOS до iOS 11 без каких-либо проблем. В версиях iOS, предшествующих 11, он заменяет / рассматривает макет безопасной области на обычный AutoLayout и следует правилам макета "Верх и низ".
Я протестировал свой существующий проект с SafeAreaLayout и без него на обеих платформах (iOS 11 и более поздняя iOS 10). Работает нормально.
Просто убедитесь:
Если вы разработали свой проект / пользовательский интерфейс в AutoLayout; ограничения вашего UIElement следуют / относятся к руководству по макету сверху и снизу (не для супервидения). Таким образом, одним щелчком (включением) опции SafeAreaLayout автоматически будет правильно реализован макет SafeArea для всех файлов Interface Builders в раскадровке.
Если вы разработали свой проект / пользовательский интерфейс в SafeAreaLayout; тогда он будет автоматически следовать указаниям вершины и низа в отсталой iOS.
Вот пример снимка с результатом. Включение или отключение макета безопасной области не влияет на существующий дизайн.
AutoLayout
Короче говоря, ответ на ваш вопрос: "Включение руководств по макету безопасной области, совместимых с iOS до 11"
Вы можете реализовать макет безопасной области в своем проекте / приложении, и он будет отлично работать с предыдущими версиями iOS, преобразовав макет безопасной области в верхний и нижний макеты.
Я использовал это в Objective-C с хорошим результатом для iOS 10. Если вы используете SafeArea в xib, вы можете добавить в свой viewDidLoad
:
if (@available(iOS 11.0, *)) {}
else {
self.edgesForExtendedLayout = UIRectEdgeNone;
}
для iOS 9:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navigationController.navigationBar.translucent = NO;
}
Если вы включите автоматическое размещение и добавите ограничения просмотра в безопасную область, вы можете перейти на iOS 11+ выше, но это может не работать с iOS 9, и ваше представление может появиться под панелью навигации. Чтобы решить эту проблему, вы можете отключить полупрозрачную атрибут в "viewWillAppear:(BOOL) анимированный" метод.
Чтобы не нарушать предыдущее состояние полупрозрачного атрибута панели навигации, вы должны сохранить предыдущее значение и повторно установить его в 'viewWillDisappear: (BOOL) animated'
@interface YourViewController ()
@property (nonatomic, assign) BOOL translucentState;
@end
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.translucentState = self.navigationController.navigationBar.translucent;
self.navigationController.navigationBar.translucent = NO;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.navigationController.navigationBar.translucent = self.translucentState;
}
PS не должен использовать EdgeForExtendedLayout для этого:
self.edgesForExtendedLayout = UIRectEdgeNone;
Ознакомьтесь с документацией Apple: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621515-edgesforextendedlayout
Я нашел более удобный способ, где вам нужно только подкласс NSLayoutConstraint
это прикреплено к вашему safeArea
,
Это немного странно, поскольку вы должны получить ViewController из UIView, но, на мой взгляд, это простая и хорошая альтернатива, пока Apple окончательно не исправит обратную совместимость для safeArea в Xibs.
Подкласс:
class SafeAreaBackwardsCompatabilityConstraint: NSLayoutConstraint {
private weak var newConstraint: NSLayoutConstraint?
override var secondItem: AnyObject? {
get {
if #available(iOS 11.0, *) {}
else {
if let vc = (super.secondItem as? UIView)?.parentViewController, newConstraint == nil {
newConstraint = (self.firstItem as? UIView)?.topAnchor.constraint(equalTo: vc.topLayoutGuide.bottomAnchor)
newConstraint?.isActive = true
newConstraint?.constant = self.constant
}
}
return super.secondItem
}
}
override var priority: UILayoutPriority {
get {
if #available(iOS 11.0, *) { return super.priority }
else { return 750 }
}
set { super.priority = newValue }
}
}
private extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
XIb:
"Руководство по разметке безопасной зоны" обратно совместимо. Ну, если вы не используете его в XIB. С раскадровкой вроде нормально.
Я решил свою проблему, открыв "Ограничение верхнего макета" из первого объекта в верхней части моего представления.
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topLayoutConstraint;
затем я изменил значение константы на это ограничение и обновил представление. Например, если вы используете панель навигации (высота 44) и строку состояния (высота 20):
if (SYSTEM_VERSION_LESS_THAN(@"11.0")) {
_topLayoutConstraint.constant = 64;
[self.view layoutIfNeeded];
}
С SYSTEM_VERSION_LESS_THAN, который определен так:
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
В Objective-C для верхнего и нижнего поля, когда на iPhone-X
if (@available(iOS 11, *)) {
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:self.childView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.parentView.safeAreaLayoutGuide
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0];
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self.childView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.parentView.safeAreaLayoutGuide
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0];
} else {
NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:self.childView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.parentView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0];
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self.childView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.parentView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0];
}
Если у вас есть универсальный ViewController, который расширяют все ваши ViewController, другим решением будет поместить элементы, которые должны быть отрегулированы, в IBOutletCollection и программно настроить их в этом GenericViewController. Вот мой код:
@IBOutlet var adjustTopSpaceViews: [UIView]?
override func viewDidLoad() {
super.viewDidLoad()
adjustViews()
....
}
func adjustViews() {
guard let views = adjustTopSpaceViews,
ProcessInfo.processInfo.operatingSystemVersion.majorVersion < 11 else {
return
}
let statusBarHeight = UIApplication.shared.statusBarFrame.height
for subview in views {
subview.superview?.constraints.filter({ (constraint) -> Bool in
return constraint.firstAttribute == .top
&& constraint.secondAttribute == .top
&& (constraint.firstItem as? UIView == subview || constraint.secondItem as? UIView == subview)
}).forEach({ (constraint) in
constraint.constant += (constraint.firstItem as? UIView == subview) ? statusBarHeight : -statusBarHeight
})
}
}
У меня проблемы с обратной совместимостью с WKWebView и безопасной областью на iOS 9. По какой-то причине WKWebView просто игнорирует настройки макета безопасной области.
Вот что я сделал с моими проектами
В моем случае оба мои topConstraint
а также bottomConstraint
с @IBOutlet
s. Это также совместимо для iOS 8
,
Моя первоначальная конфигурация для верхних и нижних ограничений - для обычных айфонов, поэтому я редактирую только ограничения для iPhone X
// iOS 11 Layout Fix. (For iPhone X)
if #available(iOS 11, *) {
self.topConstraint.constant = self.topConstraint.constant + self.view.safeAreaInsets.top
self.bottomConstraint.constant = self.bottomConstraint.constant + self.view.safeAreaInsets.bottom
}
,
НОТА: self.view
это ваш superView, поэтому я использую его для safeAreaInsets
Вот моя оболочка для решения от iOS 9 до iOS 11+ в Swift 4+
let safeAreaTopAnchor:NSLayoutYAxisAnchor?
if #available(iOS 11.0, *) {
safeAreaTopAnchor = contentView.safeAreaLayoutGuide.topAnchor
} else {
// Fallback on earlier versions
var parentViewController: UIViewController? {
var parentVCResponder: UIResponder? = self
while parentVCResponder != nil {
parentVCResponder = parentVCResponder!.next
if let viewController = parentVCResponder as? UIViewController {
return viewController
}
}
return nil
}
safeAreaTopAnchor = parentViewController?.topLayoutGuide.bottomAnchor
}
Простое решение Swift 4:
Сначала установите верхний приоритет ограничения для безопасной области на 750, затем:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 11, *) {
// Safe area constraints already set.
} else {
NSLayoutConstraint.activate([
self.yourView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor)
])
}
}