Руководство по макету безопасной области iOS 11, обратная совместимость

Совместимо ли руководство по безопасной разметке совместимо с iOS ниже 11?

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

12 ответов

Мне удалось поработать с новыми руководствами по макету Безопасной области и поддерживать обратную совместимость с iOS 9 и iOS 10:(РЕДАКТИРОВАТЬ: как указано в комментариях @NickEntin, эта реализация предполагает, что присутствует строка состояния, которая не будет Будьте верны в ландшафте на iPhone X. В результате много места до верха (20 баллов). Однако он будет работать отлично.

Например, если вы хотите, чтобы изображение было на 10 пунктов ниже строки состояния (и на 10 пунктов ниже корпуса датчика на iPhone X):

  1. В вашем XIB перейдите к File Inspector и включить сейф, проверив Use Safe Area Layout Guides,
  2. Создайте ограничение от вершины представления к вершине основного представления, с >= (больше или равно) ограничение, постоянное 30 (30, потому что мы хотим, чтобы интервал в 10 баллов к строке состояния был выше 20 баллов) и приоритет High (750).
  3. Создайте ограничение от вершины представления до вершины безопасной области, с = (равное) ограничение, постоянное 10 и приоритет Low (250).

То же самое можно сделать для вида внизу (и для движения вперед / назад или влево / вправо в безопасную зону):

  1. В вашем XIB перейдите к File Inspector и включить сейф, проверив Use Safe Area Layout Guides,
  2. Создайте ограничение от нижней части представления к основному основанию, с >= (больше или равно) ограничение, постоянное 10 и приоритет High (750).
  3. Создайте ограничение от нижней части представления до нижней части безопасной области, с = (равное) ограничение, постоянное 10 и приоритет Low (250).

Обратная совместимость Safe Areas для iOS 9 и iOS 10 работает только при использовании раскадровок. Если вы используете xibs, не существует руководства по верстке. https://forums.developer.apple.com/thread/87329

Обходные пути, кажется, либо

(а) перенести свои xibs в раскадровки, или

(б) добавить некоторые дополнительные ограничения программно.

Если (а) на самом деле не вариант, ручной подход будет выглядеть примерно так:

Предполагая, что у вас есть представление в xib, которое вы хотите оставить в безопасной зоне (то есть ниже любой строки состояния или панели навигации).

  1. Добавьте ограничения в xib между вашим представлением и безопасной областью для iOS 11. Присвойте верхнему ограничению приоритет 750.

    добавить верхнее ограничение

  2. В вашем контроллере представления добавьте свойство:

    @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.

  3. Повторите для нижнего ограничения, если вам нужно избежать индикатора дома.

Версия 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с @IBOutlets. Это также совместимо для 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)
        ])
    }
}
Другие вопросы по тегам