Итак, первый классический - обычный 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)


  1. storoj
    04.01.2023 18:16

    и элементы вашего приложения будут выглядеть всегда одинаково и на iPhone 14 Pro Max, и на iPhone SE.

    кажется, что цель изначально была неверной


    1. storoj
      04.01.2023 18:20
      +2

      Но что если ваше приложение должно работать на iPhone SE 1 поколения? Тогда с вероятностью в 100% где-то вёрстка поедет.

      если в логике вёрстки были изъяны, то да. Но далеко не у всех что-то куда-то поедет с вероятностью 100%


      1. KalininArtemVal Автор
        04.01.2023 18:27

        тут скорее речь шла про то что в высоту экраны различаются и не все что должно отображаться сразу будет отображаться в силу этого фактора) Но спасибо за замечание)


        1. storoj
          04.01.2023 18:28

          Можно пример того, что непременно "должно отображаться сразу" при живом-то UIScrollView?


          1. KalininArtemVal Автор
            04.01.2023 18:37

            Допустим у тебя профиль с определенным размером аватарка информации профиля и тд. На разных девайсах отображение всей информации будет выглядеть по-разному. Например, на большом айфоне отобразится вся информация, а на маленьком устройстве только аватарка с датой рождения. Ну это так в качестве примера)


            1. storoj
              04.01.2023 18:51

              Что же здесь ненормального? Для того скроллинг и был изобретён, чтобы размер экрана мог быть меньше размера контента.


              1. KalininArtemVal Автор
                04.01.2023 19:02

                тем не менее нельзя отрицать, что экраны iPhone SE 1 гораздо меньше любого pro max. И гораздо удобнее видеть все что необходимо на экране сразу (чуть уменьшенное например), чем просто скролить. Тем более что UI из-за этого может выглядеть как текст в ватсап у наших бабушек или дедушек)


                1. storoj
                  04.01.2023 19:04

                  Тем более что UI из-за этого может выглядеть как текст в ватсап у наших бабушек или дедушек)

                  если речь об увеличенных шрифтах, то это неправда. Если специально не портить, то размеры элементов будут одинаковыми, и на большом экране поместится больше контента. А как раз-таки в статье, как я понял, предлагается скейлить элементы (уменьшать) для разных размеров экрана. Что я считаю провальной затеей в большинстве случаев.


                  1. KalininArtemVal Автор
                    04.01.2023 19:08

                    Добрый вечер.. что значить "специально не портить, размеры будут одинаковыми". Мы об одном и том же говорим? Я пишу про UI.
                    У тебя есть аватарка 24х24, где бы ты ее не открыл, на каком угодно девайсе, она всегда будет у тебя 24х24, если нет определенных условий, например в расширении как я описал выше.
                    А если по тз она должна быть 400х400? у тебя аватарка уйдет за пределы экрана)


                    1. storoj
                      04.01.2023 19:11

                      Тогда надо или выключить голову и делать "как в ТЗ", или в тз на самом деле нет требования именно о размерах 400х400.


                      1. KalininArtemVal Автор
                        04.01.2023 19:16

                        т.е. правильно понимаю, что "выключить голову и делать"как в ТЗ"" имеется ввиду пойти на принцип и сделать заведомо плохо? дабы не прибегать к таким вариантам?)


                    1. storoj
                      04.01.2023 19:23
                      +1

                      Вообще такие расчёты в зависимости от Размера Экрана Устройства заведомо неправильные. Как минимум потому, что почему-то подразумевается исключительно полноэкранный режим, и как правило ещё и заведомо портретный. И то, и другое вдруг оказывается неправдой, а половина проекта уже подсела на эти "константы", которые вдруг константами быть перестали.

                      Поэтому наиболее жизнеспособный вариант это всё-же отталкиваться от размеров своей superview. Тогда в качестве прямой замены этих умножений и делений можно хотя бы использовать NSLayoutDimension.constraint(equalTo:multiplier:):

                      view.widthAnchor.constraint(equalTo: superview.widthAnchor, multiplier: 0.2)
                      

                      чтобы заставить view занимать 20% ширины.


                      1. KalininArtemVal Автор
                        04.01.2023 19:30
                        -2

                        пусть это будет 4й вариант)


                      1. storoj
                        04.01.2023 19:31
                        +1

                        И что делать в момент поворота экрана?


                      1. KalininArtemVal Автор
                        04.01.2023 19:32

                        что делать? )


                      1. storoj
                        04.01.2023 19:34
                        +1

                        "снимать штаны и бегать"

                        что делать с тем фактом, что ширина уже не ширина, а высота уже не высота, и то, что только что полностью помещалось в экран, уже перестало в него помещаться?


                      1. KalininArtemVal Автор
                        04.01.2023 20:39
                        -1

                        не вижу проблемы)


                1. storoj
                  04.01.2023 19:07
                  +1

                  И гораздо удобнее видеть все что необходимо на экране сразу (чуть уменьшенное например)

                  на чём основывается убеждение, что "чуть уменьшенное" это "гораздо удобнее", чем "нормального размера", такого же как во всех других приложениях?


                  1. KalininArtemVal Автор
                    04.01.2023 19:10
                    -1

                    субъективно. Конкретные примеры искать не буду) Про вотсап уже писал)


  1. storoj
    04.01.2023 18:19
    +2

    extension UIDevice {
        var current: ScreenType {
            switch UIScreen.main.nativeBounds.height {
    

    расширяется код инстанса UIDevice, а данные читаются из UIScreen.main. Может тогда надо было в UIScreen и добавлять код определения типа экрана?


    1. KalininArtemVal Автор
      04.01.2023 18:28

      Этот пример из старого кода, со старого проекта)


      1. storoj
        04.01.2023 18:30
        +1

        Наверное стоило поднапрячься, чтобы пример из старого проекта не попадал другим людям в новые проекты с новым кодом.


        1. KalininArtemVal Автор
          04.01.2023 18:31
          -2

          все равно он будет работать)


  1. debug45
    05.01.2023 08:44

    При смене ориентации экрана или подключении устройства ко внешнему монитору вся кастомная логика, завязанная на ‘UIDevice’ и ‘UIScreen.main’, сразу упорется


    1. KalininArtemVal Автор
      05.01.2023 12:38
      -2

      Какой процент приложений поддерживают смену ориентации? А я описываю свой опыт работы в разных проектах в разных компаниях, где подобные механизмы работали)