- iPhone X и тайны SafeArea
- Что такое Safe Area?
- Новое API
- UIViewController
- UIView
- UIScrollView
- UITableView
- UICollectionViewFlowLayout
- Safe Area: upgrade!
- Safe Area и iOS 9, 10
- Margin + Safe Area
- max(margin, safeAreaInset)
- min(margin, safeAreaInset)
- Safe Area: Stop, go back!
- UITableView + Safe Area
- Safe Area: Stop, I said, go back!
- Как адаптировать игру на Unity под iPhone X к апрелю
- Теория
- Практика
- Как это выглядит у нас
iPhone X и тайны SafeArea
iPhone X принес нам новую, неожиданную особенность, связанную с новой формой экрана: Safe Area.
Что такое Safe Area?
Safe Area была придумана, чтобы контент приложения не наезжал на элементы корпуса и на статусбар. На самом деле, это всего лишь UILayoutGuide , который есть внутри каждой UIView , и который является пересечением Safe Area родителя и своего фрейма.
Новое API
В некоторых системных вьюхах ( UIScrollView , UITableView ) есть специальные флаги, которые позволяют им работать“из коробки”, учитывая её.
Рассмотрим подробнее эти флаги, и остальное новое API, свазянное с Safe Area.
UIViewController
Это свойство используется для изменения Safe Area в UIViewController ’е. Его можно использовать, например, чтобы избежать наложения контента UIVIew , принадлежащие вашему UIViewController ’у. Хорошо понятно, зачем дополнительные инсеты нужны в UINavigationController и UITabBarController — они используют эти инсеты для того, чтобы контент не наезжал на UINavigationBar / UITabBar .
Переопределив этот метод, можно получать уведомления об изменении Safe Area у корневой UIView , что позволяет вам учитывать Safe Area, если вы не используете лейаут на констрейнтах.
UIView
Это свойство позволяет получить точные значения отступов для Safe Area, что необходимо, если вы программно меняете размеры/позицию элементов интерфейса.
В случае, когда UIView находится за пределами экрана, или еще не находится в иерархии, значения отступов равны 0.
Свойство, о котором идет речь в этой статье.
Этот метод можно переопределить, если вы хотите подстраивать контент вашей UIView под Safe Area программно.
Если этот флаг выставлен в true, то все margin’ы, которые находятся вне Safe Area будут автоматически исправлены таким образом, чтобы попадать в неё.
UIScrollView
С помощью этого свойства можно определить тип поведения отступов контента UIScrollView
case never: Не добавлять никаких отступов.
case always: Всегда добавлять отступы так, чтобы контент полностью находился в SafeArea.
case scrollableAxes: Автоматически добавлять отступы так, чтобы контент полностью находился в SafeArea, но только по осям, по которым разрешен скролл.
case automatic: Значение по-умолчанию. По-горизонтали добавляет отступы всегда, когда ось скроллится. По-вертикали добавляет отступы либо, если ось скроллится, либо когда UIScrollView — первая подвью в UIViewController ’е, и он показан в UINavgationController ’е или UITabBarController ’е.
Это read-only свойство позволяет узнать финальные значения отступов в UIScrollView, и состоит оно из отступов, определенных поведением выше и отступов, взятых из contentInset ‘ов, которые можно задать программно.
Так же, как и в аналогичных функциях выше — её можно использовать, чтобы получать уведомления о смене отступов.
UITableView
var insetsContentViewsToSafeArea: Bool = true
Этот флаг позволяет автоматически добавлять отступы для contentView в UITableViewCell , так, чтобы contentView полностью лежала в Safe Area. Стоит заметить, что отступ сепаратора слева учитывает Safe Area, но не справа.
UICollectionViewFlowLayout
var sectionInsetReference: UICollectionViewFlowLayoutSectionInsetReference = .fromContentInset
Это свойство позволяет задать, откуда будет отсчитываться отступ в секции:
case fromContentInset: от contentInset’ов
case fromSafeArea: от Safe Area
case fromLayoutMargins: от layoutMargin’ов (которые, в свою очередь, отсчитываются от Safe Area)
Safe Area: upgrade!
Рассмотрим теперь более сложные и интересные случаи обработки Safe Area, когда стандартные системные флаги не помогут.
Safe Area и iOS 9, 10
Давайте представим, что нам необходимо поддерживать приложение и для более ранних версий iOS. Что делать? Первой остановкой будет Interface Builder. Известно, что в нём есть возможность учитывать Safe Area и на более ранних версиях iOS. В этом случае учитывается только статусбар, навбар и таббар. Но многие, по тем или иным причинам, отказываются от Interface Builder’а, и, в этом случае, вам нужно в корневых вьюхах ваших UIViewController’ов использовать topLayoutGuide и bottomLayoutGuide :
Из других интересных примеров: для сохранения обратной совместимости пришлось во всем проекте включить contentInsetAdjustementBehavior == .never , так как во многих случаях мы опирались в расчетах на то, что мы сами задаем contentInset ’ы и реальные значения отступов будут им равны.
Margin + Safe Area
Следующий случай, который мы рассмотрим, будет довольно простым: когда вью должна отстоять от родительского UIView на margin.
В таком случае нужно всего лишь создать хелпер с простым if #available <…>else <…>:
max(margin, safeAreaInset)
Следующий случай представляется более интересным: возьмем, для примера, кнопку “Поехали!”. Необходимо было, чтобы она находилась в max(margin, safeAreaInset) от нижнего края экрана.
Получаем два варианта:
- Когда Safe Area больше margin ’а, вьюха должна прикрепляться к Safe Area
- Когда margin больше Safe Area, вьюха должна находиться на расстоянии соответствующего margin ’а.
Решений тут можеть быть несколько: первое, самое простое, заключается в том, что мы оборачиваем наш вью в контейнер-вью, контейнер прикрепляем к краю экрана с нужным margin ’ом, а нашу вьюху прикрепляем к Safe Area внутри контейнера, а в случае более старых версий iOS — к самому контейнеру.
Однако, есть еще один вариант, который не требует создания дополнительной вьюхи, который основан на приоритетах NSLayoutConstraint ’ов:
After solving for the required constraints, Auto Layout tries to solve all the optional constraints in priority order from highest to lowest. If it cannot solve for an optional constraint, it tries to come as close as possible to the desired result, and then moves on to the next constraint.
Проще говоря, констрейнты, которые не могут быть удовлетворены, будут максимально приближены к желаемому результату:
Последний констрейнт нужен для того, чтобы при наличии констрейнта на верх вьюхи, она бы не уехала наверх (констрейн на верх вьюхи должен иметь приоритет .defaultHigh — 1 или ниже).
min(margin, safeAreaInset)
Еще более редкий случай — когда нужно, чтобы вьюха находилась на min(margin, safeAreaInset) от края экрана. Довольно редкий случай, но в картах нашелся и он.
Для достижения цели, нужно всего лишь добавить два констрейнта c отношением .lessThanOrEqual / .greaterThanOrEqual (в зависимости от края):
Safe Area: Stop, go back!
UITableView + Safe Area
В большинстве случаев, обработка Safe Area в UITableView ложится на плечи включенного по-дефолту флага insetsContentViewsToSafeArea . Но, этот флаг не будет работать для кастомных хедеров и футеров. Первым решением будет приаттачить весь контент к Safe Area:
Но это приведет к плачевным результатам:
Разберемся подробнее. Оказывается, отступы от границы до Safe Area не могут быть больше, чем соответствующие отступы у superview . Это сделано для того, чтобы в случае, когда UIView находится не полностью во фрейме superview , у нее не станет вырожденной Safe Area. Проверим нашу мысль на обычном UIScrollView:
Действительно. Но, в таком случае, как работать с Safe Area в таких условиях? Довольно просто: не используйте Safe Area в направлениях скролла.
Safe Area: Stop, I said, go back!
Однако, это не все трюки, которые приготовила нам Safe Area.
Если мы добавляем вьюконтроллер таким образом, что хотя бы его часть скрыта, то мы натыкаемся на баг iOS, и этот UIViewController не будет учитывать SafeArea. Стоит заметить, что Apple не торопится исправить этот баг, хотя на такое поведение жаловались на Stack Overflow и у них есть стабильно воспроизводящийся пример. Самый простой способ наткнуться на этот баг с амому — добавить в UIScrollView несколько вьюх вьюконтроллеров.
В заключение хочется сказать, что Safe Area хранит в себе еще много нераскрытых тайн, надо не сдаваться и помнить: Истина где-то рядом.
Источник
Как адаптировать игру на Unity под iPhone X к апрелю
Месяц назад Apple предупредила всех разработчиков, что с апреля все приложения, которые заливаются в App Store, должны быть собраны с использованием последнего iOS 11 SDK. О том, как правильно позиционировать контент, используя новый API, уже давно можно почитать в официальной документации и Human Interface Guidelines. А хорошими и подробными статьями об адаптации игр на Unity ни на русском, ни на английском языке нас не радуют. А так как в War Robots поддержка нового UI появилась с февральским релизом версии 3.6.0, я решил написать собственный гайд со скриптами и скриншотами.
Теория
Начнем с того, что любая игра на Unity собирается под iOS сначала системой сборки самого движка. На выходе вы получаете обычный Xcode-проект. Допустим, сейчас сентябрь 2017, вы привычно работаете в Xcode 8.3 и успешно отправляете релизы вашей игры в App Store. Что же будет, если в этот момент в продажу поступит устройство с экраном, которого до этого не существовало? Все банально просто: проект, собранный в Xcode старых версий, а именно ниже версии 9.0, получит большие черные полосы сверху и снизу. А в landscape-ориентации в качестве бонуса к двум полосам по бокам достанется еще и такой же черный отступ снизу, чтобы обеспечить достаточно свободного места для нового элемента интерфейса iOS, эксклюзивного для iPhone X — контрастной горизонтальной полосы, заменяющей кнопку Home.
Таким образом Apple заботливо уберегла пользователей от немыслимого числа приложений, которыми нельзя пользоваться из-за кнопок, отрисованных в углах или под козырьком. Но выглядит это совсем не секси, совсем не Human Interface Guidelines.
Тут же в голову приходит соблазнительная идея: установить Xcode 9 и собрать свой проект уже с его помощью. Казалось бы, это и есть та самая серебряная пуля, но увы, во многих случаях результат может оказаться еще хуже.
Дело в том, что в этом случае вы уже сами берете на себя ответственность, а Apple не мешает вам сделать неюзабельное приложение. Важные элементы интерфейса, которые раньше уютно располагались по углам и вдоль различных сторон экрана, теперь оказались обрезаны и закрыты Face ID или кнопкой Home. Само собой, никакой магии не произошло, экран iPhone по-прежнему прямоугольный, а видеокарте нет никакого дела до этих дизайнерских нововведений.
Практика
Но решение есть. В iOS 11 компания Apple представила новое property UIView, которое позволяет получить UIEdgeInsets. Он будет содержать в себе значения безопасных отступов от каждой из сторон экрана:
Весь кайф в том, что для всех устройств, кроме iPhone X, будут возвращаться нулевые отступы, а значит вам не придется огораживать код условиями для каких-то конкретных случаев, все будет работать одинаково хорошо на всем парке девайсов. Зная об этом, вам остается получить искомые значения в проекте Unity.
Во-первых, Apple рекомендует адаптировать игры таким образом, чтобы информационный контент занимал все доступное пространство, а смещались только интерактивные элементы управления. Скорее всего, у вас они уже находятся в отдельном Canvas и их имеет смысл выделить в RectTransform, отдельный от декоративных и требующих всей плоскости экрана элементов. Оставшиеся изменения не представляют большой сложности. В реализации этого поможет workaround решение от Unity из их репозитория на BitBucket.
Мы немного доработали скрипт SetCanvasBounds, заменив вызов метода Update() вызовом Awake(), поскольку значения отступов не меняются, а элементы UI в нашей игре всегда находятся на одних и тех же местах. Достаточно только один раз установить нужные отступы компоненту RectTransform и все будет выглядеть правильно в любой ситуации.
В этом скрипте в методе GetSafeArea() вызывается нативный C-метод
объявленный в файле SafeAreaImpl.mm. Именно здесь, в соседнем методе, вы можете найти тот самый API, о котором было упомянуто выше.
Используйте API Screen.safeArea вместо вызова нативного метода, если ваша версия Unity его поддерживает. В скрипте SetCanvasBounds заботливо оставлен соответствующий комментарий.
Получив таким образом значения отступов, мы устанавливаем их нашему RectTransform, в котором содержатся и правильно позиционируются UI элементы. Осталось всего лишь повторить это на каждом UI-элементе, который требует таких изменений.
Как это выглядит у нас
Чтобы продемонстрировать результат на примере реального проекта, мы сделали скриншоты трех различных сборок нашей игры: Xcode 8, Xcode 9 без поддержки iPhone X и Xcode 9 с поддержкой iPhone X.
Xcode 8
Xcode 9 без поддержки iPhone X
Xcode 9 с поддержкой iPhone X
На скриншотах сборки из старой версии Xcode появились полосы вдоль трех сторон экрана. Невооруженным глазом видно, что отступ слева и справа неоправданно велик. Играть в динамичный шутер с такими рамками оказалось не очень удобно. Надо было от них избавляться.
Потом, когда мы просто собрали игру в Xcode 9, полосы исчезли как страшный сон. Но теперь станет заметно, что часть наших кнопок утонула под козырьком нового iPhone. Другие же пали жертвами углов. Надо было спасать UI от причудливых форм флагмана Apple.
Мы использовали описанные в этой статье приемы и собрали игру в третий раз. Снова в Xcode 9. Вот этот результат полностью удовлетворил наши ожидания. Кнопки в ангаре находятся на своих местах и не прячутся за углами, а интерфейс боя стал удобнее, не потеряв своей привлекательности. На этом варианте мы и остановились, так как он полностью соответствует требованиям Human Interface Guidelines, после чего отправили очередной релиз в App Store, чего желаю и вам, если вы еще не успели этого сделать.
Источник