Руководства по макету безопасной зоны в xib-файлах — iOS 10

Я начал адаптировать свое приложение для iPhone X и обнаружил проблему в Interface Builder. Согласно официальным видеороликам Apple, руководства по размещению безопасных зон должны быть обратно совместимыми. Я обнаружил, что это прекрасно работает в раскадровках.

Но в моих файлах XIB руководства по макету безопасной области не соблюдаются в iOS 10.

Они отлично работают для новой версии ОС, но устройства iOS 10, похоже, просто принимают расстояние в безопасной зоне равным нулю (игнорируя размер строки состояния).

Я пропустил какую-либо необходимую конфигурацию? Является ли это ошибкой Xcode, и если да, то какие-либо известные обходные пути?

Вот скриншот проблемы в тестовом проекте (слева iOS 10, справа iOS 11):

выравнивание безопасной зоны поверх экрана


person Tiago Lira    schedule 19.09.2017    source источник


Ответы (9)


Есть некоторые проблемы с макетом безопасной области и обратной совместимостью. См. мой комментарий по здесь.

Возможно, вы сможете обойти проблемы с помощью дополнительных ограничений, таких как приоритет 1000 >= 20.0 для superview.top и приоритет 750 == safearea.top. Если вы всегда показываете строку состояния, это должно исправить ситуацию.

Лучшим подходом может быть создание отдельных раскадровок/xib для версий до iOS 11 и iOS-11 и выше, особенно если вы сталкиваетесь с большим количеством проблем. Причина, по которой это предпочтительнее, заключается в том, что в версиях до iOS 11 вы должны размещать ограничения по верхним/нижним направляющим макета, но для iOS 11 вы должны размещать их в безопасных областях. Направляющие макета исчезли. Разметка по макетам для версий до iOS 11 стилистически лучше, чем простое смещение минимум на 20 пикселей, даже если результаты будут такими же, если вы всегда показываете строку состояния.

Если вы выберете этот подход, вам нужно будет установить для каждого файла правильную цель развертывания, на которой он будет использоваться (iOS 11 или что-то более раннее), чтобы Xcode не давал вам предупреждений и позволял вам использовать руководства по макету или безопасные зоны, в зависимости. В своем коде проверьте наличие iOS 11 во время выполнения, а затем загрузите соответствующую раскадровку/xibs.

Недостатком этого подхода является обслуживание (у вас будет два набора ваших контроллеров представления для поддержки и синхронизации), но как только ваше приложение поддерживает только iOS 11+ или Apple исправит генерацию ограничений руководства по макету обратной совместимости, вы можете получить избавиться от версий до iOS 11.

Кстати, как вы показываете контроллер, с которым вы это видите? Это просто контроллер корневого представления или вы его представили, или..? Проблема, которую я заметил, связана с нажатием контроллеров представления, поэтому вы можете столкнуться с другим случаем.

person clarus    schedule 21.09.2017
comment
Моя конкретная проблема связана только с файлами XIB и, похоже, возникает как для нажатых, так и для представленных контроллеров. Ваш обходной путь с несколькими ограничениями звучит как хорошее решение для большинства ситуаций. Спасибо! Я все еще надеюсь, что они решат эти проблемы до появления iPhoneX. - person Tiago Lira; 21.09.2017
comment
Большое спасибо за приоритетное предложение! Теперь прекрасно работает на iOS 9 и iOS 11 под iPhone X. - person alex.bour; 21.09.2017
comment
Могу ли я прокомментировать, спросить об этом Если вы всегда показываете строку состояния, это должно исправить ситуацию.? Я не вижу способа отобразить строку состояния на симуляторе iPhone X в альбомной ориентации. Тогда описанный обходной путь с приоритетом 1000 ›= 20,0 для superview.top и приоритетом 750 == safearea.top будет иметь зазор в 20 пикселей сверху для iPhone X в альбомной ориентации. Мое решение состояло в том, чтобы удалить ограничение приоритета 1000 ›= 20.0 в viewDidLoad, если iOS ‹ 11. Я упустил способ принудительно включить строку состояния в альбомной ориентации iPhone X? - person Stanislav Dvoychenko; 28.09.2017
comment
Неплохо подмечено. Я имел дело только с портретом для приложения, с которым работал. В iOS 11 в любом случае нет причин для этого ограничения. Это звучит как хорошее решение или программно добавить ограничение верхнего макета для ‹ ios 11 и не всегда иметь это ›= 20. - person clarus; 28.09.2017
comment
@clarus, тоже хороший момент. Вероятно, зависит от того, насколько явной/неявной потребуется эта обработка. В конце концов, я решил оставить xib с безопасной областью, приоритетом 1000 ›= 20.0 в xib и убрать ограничение в (если (@доступно(iOS 11.0, *))). У меня есть только 2 таких xib, используемых для многих контроллеров (наследование VC, поэтому я не могу просто перенести их на раскадровки). В случае с этими двумя я, вероятно, предпочту, чтобы все было очень явным как в xib, так и в коде. Если бы у меня были десятки таких, я бы, вероятно, выбрал предложенный вами порядок, с точки зрения будущего обслуживания. - person Stanislav Dvoychenko; 28.09.2017
comment
Упомянутая вами установка приоритета — лучшее, что я видел за всю неделю. Это сводило меня с ума. Спасибо. - person AnBisw; 10.11.2017
comment
Я думаю, что отдельные раскадровки - не очень хорошее решение. Я бы предпочел написать больше кода, чтобы избежать этого - person C0D3; 03.02.2018
comment
Вместо того, чтобы давать двойное ограничение, Apple должна обеспечить обратную совместимость для ios‹11 @clarus, или это может быть включено в wwdc 2018 ? - person Jack; 29.05.2018
comment
Отличное решение, спасибо! Однако один вопрос: при добавлении ограничения superview.top должно ли оно быть относительно полей или нет? - person jowie; 29.06.2018

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

Мое решение состоит в том, чтобы создать 2 ограничения в построителе интерфейса и удалить одно в зависимости от используемой вами версии ios:

  • для iOS 11: view.top == safe area.top
  • для более ранних версий: view.top == superview.top + 20

Добавьте их как розетки как myConstraintSAFEAREA и myConstraintSUPERVIEW соответственно. Потом:

override func viewDidLoad() {
    if #available(iOS 11.0, *) {
        view.removeConstraint(myConstraintSUPERVIEW)
    } else {
        view.removeConstraint(myConstraintSAFEAREA)
    }
}
person Martin Massera    schedule 27.12.2017
comment
Это гораздо более удобный/чистый подход, чем принятый ответ, imo. Обратите внимание, что это зависит от наличия строки состояния всегда - person Jesse Naugher; 17.04.2018
comment
Я согласен - искал ответы, и это кажется более чистым, чем отдельные файлы XIB для iOS 10 и iOS 11. - person Brian Sachetta; 31.07.2018

Для меня простое исправление, позволяющее заставить его работать в обеих версиях, было

    if #available(iOS 11, *) {}
    else {
        self.edgesForExtendedLayout = []
    }

Из документации: «В iOS 10 и более ранних версиях используйте это свойство, чтобы сообщить, какие края вашего контроллера представления простираются под панелями навигации или другими представлениями, предоставляемыми системой». Таким образом, установка их в пустой массив гарантирует, что контроллер представления не расширяется под панелями навигации.

Документ доступен здесь

person Falco Winkler    schedule 09.07.2018
comment
К сожалению, это работает, только если панель навигации видна. Если панель навигации скрыта, вид все равно будет расширяться под строку состояния с помощью edgesForExtendedLayout = [] - person OliverD; 08.07.2019

Я объединил некоторые ответы с этой страницы в это, что работает как шарм (только для верхнего руководства по макету, как это было запрошено в вопросе):

  1. Обязательно используйте безопасную область в раскадровке или xib-файле.
  2. Ограничьте свои взгляды безопасными зонами
  3. For each view which has a constraint attached to the SafeArea.top
    • Create an IBOutlet for the view
    • Создайте IBOutler для constraint
  4. Внутри ViewController на viewDidLoad:

    if (@available(iOS 11.0, *)) {}
    else {
        // For each view and constraint do:
        [self.view.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor].active = YES;
        self.constraint.active = NO;
    }
    

Редактировать:

Вот улучшенная версия, которую я использовал в нашей кодовой базе. Просто скопируйте/вставьте приведенный ниже код и подключите каждое представление и ограничения к их IBOutletCollection.

@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *constraintsAttachedToSafeAreaTop;
@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *viewsAttachedToSafeAreaTop;


if (@available(iOS 11.0, *)) {}
else {
    for (UIView *viewAttachedToSafeAreaTop in self.viewsAttachedToSafeAreaTop) {
        [viewAttachedToSafeAreaTop.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor].active = YES;
    }
    for (NSLayoutConstraint *constraintAttachedToSafeAreaTop in self.constraintsAttachedToSafeAreaTop) {
        constraintAttachedToSafeAreaTop.active = NO;
    }
}

Количество всех IBOutletCollection должно быть одинаковым. например для каждого вида должно быть связанное с ним ограничение

person Tumata    schedule 26.07.2018

В итоге я удалил ограничение на безопасную область, которое у меня было в моем файле xib. Вместо этого я сделал выход к рассматриваемому UIView и из кода подключил его вот так, в viewDidLayoutSubviews.

let constraint = alert.viewContents.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor, constant: 0)
constraint.priority = 998
constraint.isActive = true

Это привязывает небольшое «предупреждение» к верхней части экрана, но гарантирует, что представление содержимого в предупреждении всегда находится ниже верхней безопасной области (iOS11ish)/topLayoutGuide (iOS10ish)

Простое и одноразовое решение. Если что-то сломается, я вернусь ????.

person Jonny    schedule 09.11.2017
comment
Я не думаю, что это хорошая идея, потому что, поскольку viewDidLayoutSubviews вызывается при любом изменении границ, это создаст новое ограничение и активирует его при каждом изменении границ. - person Tumata; 25.07.2018
comment
Да, вам нужно убедиться, что если он существует, вы не создадите еще один. - person Jonny; 26.07.2018

Это также работает:

override func viewDidLoad() {
    super.viewDidLoad()

    if #available(iOS 11.0, *) {}
    else {
        view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height - 80).isActive = true
        view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width - 20).isActive = true
    }
}
person Menan Vadivel    schedule 12.07.2018

Я добавил подкласс NSLayoutConstraint, чтобы решить эту проблему (IBAdjustableConstraint), с переменной @IBInspectable, выглядит так.

class IBAdjustableConstraint: NSLayoutConstraint {

    @IBInspectable var safeAreaAdjustedConstant: CGFloat = 0 {
        didSet {
            if OS.TenOrBelow {
                constant += safeAreaAdjustedConstantLegacy
            }
        }
    }
}

И OS.TenOrBelow

struct OS {
    static let TenOrBelow = UIDevice.current.systemVersion.compare("10.9", options: NSString.CompareOptions.numeric) == ComparisonResult.orderedAscending
}

Просто установите его в качестве подкласса вашего ограничения в IB, и вы сможете вносить изменения, специфичные для iOS11. Надеюсь, это поможет кому-то.

person Alex Brown    schedule 21.08.2018
comment
Вы можете помочь мне с задачей c - person Maulik shah; 21.09.2018

Я использовал этот, добавил макет верхней безопасной зоны и подключился к розетке.

@IBOutlet weak var topConstraint : NSLayoutConstraint!

override func viewDidLoad() {
    super.viewDidLoad()
    if !DeviceType.IS_IPHONE_X {
        if #available(iOS 11, *)  {
        }
        else{
            topConstraint.constant = 20
        }
    }
}
person little    schedule 27.08.2018

Нашел самое простое решение - просто отключить safe area и использовать topLayoutGuide и bottomLayoutGuide + добавить исправления для iPhone X. Возможно это не красивое решение, но требует как можно меньше усилий

person Vyachaslav Gerchicov    schedule 24.10.2017