Итак, первый классический - обычный NSLayoutConstraint
. Удобный, нативный и нисколько не обременяющий в написании. Но что если ваше приложение должно работать на iPhone SE 1 поколения? Тогда с вероятностью в 100% где-то вёрстка поедет. Для этого случая вы можете использовать UIDevice.current.
Второй помощник - UIDevice.current
. Эта переменная, которую вы можете сами прописать в extension
UIDevice.
Она позволяет вам высчитывать размеры текущего устройства и исходя из этого создавать другие переменные и делегировать устройства по группам: таким, как isSmallScreen
/isXScreenDevice
. Но даже этого не всегда бывает достаточно, и приходится отказываться от разного рода дизайнерских решений.
extension UIDevice {
var iPhone: Bool {
return UIDevice().userInterfaceIdiom == .phone
}
enum ScreenType: String {
case iPhone8
case iPhone8Plus
case iPhoneX
case iPhoneXSMax
case iPhone11
case iPhone11Pro
case iPhoneSE
case iPhone12
case iPhone12Pro
case iPhone12Mini
case Unknown
}
var current: ScreenType {
guard iPhone else { return .Unknown}
switch UIScreen.main.nativeBounds.height {
case 1136:
return .iPhoneSE
case 1334:
return .iPhone8
case 2208:
return .iPhone8Plus
case 2436:
return .iPhoneX
case 2521:
return .iPhone12
case 2532:
return .iPhone11Pro
case 2688:
return .iPhoneXSMax
case 2778:
return .iPhone12Pro
case 1792:
return .iPhone11
default:
return .Unknown
}
}
}
И третий, о котором я узнал лишь на первой работе и больше нигде его не применял. Для этого мы создаём константы screenHeight
и screenWidth
, которые равны UIScreen.main.bounds.height/width
. При использовании в NSLayoutConstraint
придерживаемся такой формулировки:
// Для вертикальных констрейнтов (например topAnchor, bottomAnchor)
20/812*screenHeight
// Для горизонтальных констрейнтов (например leadingAnchor, trailingAnchor)
20/375*screenWidth
Этот вариант практически идеально позволяет распределить UI-объекты на разных устройствах, и элементы вашего приложения будут выглядеть всегда одинаково и на iPhone 14 Pro Max, и на iPhone SE.
При первом взгляде это самый подходящий вариант. Однако чем чаще его используешь, тем больше приходит понимание, что это не панацея, а скорее не нужный, объёмный "костыль", который заполоняет весь код магическими числами и непременно вызовет негодование у вашего тимлида. Поэтому я бы рекомендовал его использовать только в редких случаях.
NSLayoutConstraint.activate([
secondTitleLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -156/812*screenHeight),
secondTitleLabel.rightAnchor.constraint(equalTo: view.rightAnchor),
secondTitleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
secondTitleLabel.leftAnchor.constraint(equalTo: view.leftAnchor)
])
NSLayoutConstraint.activate([
mainTitleLabel.bottomAnchor.constraint(equalTo: secondTitleLabel.topAnchor, constant: -32/375*screenWidth),
mainTitleLabel.rightAnchor.constraint(equalTo: view.rightAnchor),
mainTitleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
mainTitleLabel.leftAnchor.constraint(equalTo: view.leftAnchor)
])
В заключение скажу, что спустя время я чаще прибегаю к обычной работе констрейнтов, и их всегда хватает с головой. Иногда в редких случаях я использую UIDevice.current
. И практически никогда третий вариант.
Экспериментируйте! Если найдёте для себя полезным тот или иной вариант, попробуйте его в своём проекте. Если я упустил что-то, не стесняйтесь поделиться этим в комментариях.
Надеюсь, эта статья была для вас полезна.
Комментарии (25)
storoj
04.01.2023 18:19+2extension UIDevice { var current: ScreenType { switch UIScreen.main.nativeBounds.height {
расширяется код инстанса
UIDevice
, а данные читаются изUIScreen.main
. Может тогда надо было вUIScreen
и добавлять код определения типа экрана?KalininArtemVal Автор
04.01.2023 18:28Этот пример из старого кода, со старого проекта)
storoj
04.01.2023 18:30+1Наверное стоило поднапрячься, чтобы пример из старого проекта не попадал другим людям в новые проекты с новым кодом.
debug45
05.01.2023 08:44При смене ориентации экрана или подключении устройства ко внешнему монитору вся кастомная логика, завязанная на ‘UIDevice’ и ‘UIScreen.main’, сразу упорется
KalininArtemVal Автор
05.01.2023 12:38-2Какой процент приложений поддерживают смену ориентации? А я описываю свой опыт работы в разных проектах в разных компаниях, где подобные механизмы работали)
storoj
кажется, что цель изначально была неверной
storoj
если в логике вёрстки были изъяны, то да. Но далеко не у всех что-то куда-то поедет с вероятностью 100%
KalininArtemVal Автор
тут скорее речь шла про то что в высоту экраны различаются и не все что должно отображаться сразу будет отображаться в силу этого фактора) Но спасибо за замечание)
storoj
Можно пример того, что непременно "должно отображаться сразу" при живом-то
UIScrollView
?KalininArtemVal Автор
Допустим у тебя профиль с определенным размером аватарка информации профиля и тд. На разных девайсах отображение всей информации будет выглядеть по-разному. Например, на большом айфоне отобразится вся информация, а на маленьком устройстве только аватарка с датой рождения. Ну это так в качестве примера)
storoj
Что же здесь ненормального? Для того скроллинг и был изобретён, чтобы размер экрана мог быть меньше размера контента.
KalininArtemVal Автор
тем не менее нельзя отрицать, что экраны iPhone SE 1 гораздо меньше любого pro max. И гораздо удобнее видеть все что необходимо на экране сразу (чуть уменьшенное например), чем просто скролить. Тем более что UI из-за этого может выглядеть как текст в ватсап у наших бабушек или дедушек)
storoj
если речь об увеличенных шрифтах, то это неправда. Если специально не портить, то размеры элементов будут одинаковыми, и на большом экране поместится больше контента. А как раз-таки в статье, как я понял, предлагается скейлить элементы (уменьшать) для разных размеров экрана. Что я считаю провальной затеей в большинстве случаев.
KalininArtemVal Автор
Добрый вечер.. что значить "специально не портить, размеры будут одинаковыми". Мы об одном и том же говорим? Я пишу про UI.
У тебя есть аватарка 24х24, где бы ты ее не открыл, на каком угодно девайсе, она всегда будет у тебя 24х24, если нет определенных условий, например в расширении как я описал выше.
А если по тз она должна быть 400х400? у тебя аватарка уйдет за пределы экрана)
storoj
Тогда надо или выключить голову и делать "как в ТЗ", или в тз на самом деле нет требования именно о размерах 400х400.
KalininArtemVal Автор
т.е. правильно понимаю, что "выключить голову и делать"как в ТЗ"" имеется ввиду пойти на принцип и сделать заведомо плохо? дабы не прибегать к таким вариантам?)
storoj
Вообще такие расчёты в зависимости от Размера Экрана Устройства заведомо неправильные. Как минимум потому, что почему-то подразумевается исключительно полноэкранный режим, и как правило ещё и заведомо портретный. И то, и другое вдруг оказывается неправдой, а половина проекта уже подсела на эти "константы", которые вдруг константами быть перестали.
Поэтому наиболее жизнеспособный вариант это всё-же отталкиваться от размеров своей superview. Тогда в качестве прямой замены этих умножений и делений можно хотя бы использовать NSLayoutDimension.constraint(equalTo:multiplier:):
чтобы заставить
view
занимать 20% ширины.KalininArtemVal Автор
пусть это будет 4й вариант)
storoj
И что делать в момент поворота экрана?
KalininArtemVal Автор
что делать? )
storoj
"снимать штаны и бегать"
что делать с тем фактом, что ширина уже не ширина, а высота уже не высота, и то, что только что полностью помещалось в экран, уже перестало в него помещаться?
KalininArtemVal Автор
не вижу проблемы)
storoj
на чём основывается убеждение, что "чуть уменьшенное" это "гораздо удобнее", чем "нормального размера", такого же как во всех других приложениях?
KalininArtemVal Автор
субъективно. Конкретные примеры искать не буду) Про вотсап уже писал)