Введение в Size Classes в Xcode 6

Привет всем! Сегодня хотелось бы сделать небольшое введение в такую штуку, как Size Classes. Она появилась недавно вместе с Xcode 6, документации по ней от самой Apple совсем немного.

Итак, для чего же предназначена Size Classes? Все мы знаем, что на подходе уже iPhone 6 двумя (как минимум) разными размерами дисплея (4,7 и 5,5), после чего разработчикам еще больше придется заморачиваться с версткой UI для них + само собой расширения iPad«ов. В итоге количество всех поддерживаемых экранов будет около 7 (маленький привет Android). Герой сегодняшнего дня — Size Classes — как раз и предназначен для того, что бы помочь решить данную проблему.

Создадим тестовый пример в Xcode 6. Выбирем SingleViewApplication, в пункте Devices ставим Universal:

image

Далее выбираем наш Main.storyboard и в вкладке File Inspector ставим галочку напротив Use Size Classes (если она не выбрана по умолчанию):

image

Теперь обратим внимание на наш storyboard, он не совсем такой, каким мы привыкли его видеть:

image

После того, как был выбран Use Size Classes, ViewController принял некий абстрактный (базовый) размер, с обозначением wAny hAny (любая высота, любая ширина) о которой поговорим чуть дальше.

Немного теории. Каждый созданый ViewController имеет свой UITraitCollection объект (предоставляющий подробную информацию о характеристиках ViewController, доступен с iOS 8, подробней можно почитать тут), который в свою очередь имеет 2 Size Class: горизонтальный и вертикальный. Каждый Size Class может иметь 3 возможных значения: компактный (compact), обычный (regular) и любой (any). Эти значения изменяются в зависимости от устройства, а также его ориентации. Тоесть, приложение будет иметь свой отдельный интерфейс для каждого ViewController основываясь на текущих Size Class.

Возвращаясь к нашему wAny hAny, что же это? Это не что иное как сетка выбора конфигурации, для работы с определенным типом устройства, которая выглядит следующим образом:

image

При наведении курсора на сетку Apple дает подсказки разработчику при каких размерах какие устройства имеются ввиду. Общая картина сетки выглядит таким образом:

Базовые значения (any Width | any Height):

image

iPhone ландшафтный режим (compact Width | compact Height):

image

iPhone портретный режим (compact Width | regular Height):

image

iPad ландшафтный и портретный режим (regular Width | regular Height):

image

Нужно обратить внимание на зеленые точки посредине каждого квадрата сетки. Они показывают к каким устройствам будут применены изменения, которые вы делаете в данной конфигурации. Например, при базовых значениях (any Width | any Height) точки показывают разработчику что изменения будут применятся ко всем типам устройств. По этой причине Apple, рекомендует делать большую часть работы с интерфейсом именно в этой конфигурации.

Вернемся к нашему тестовому примеру. Как было сказано выше сейчас у нашего ViewController базовый Size Class (any Width | any Height). Добавим на наш ViewController 3 простых View размером 100 на 100, окрасив их в зеленый цвет, также добавим UILabel и UIButton шириной 600 и высотой 50 также окрасив их для наглядности:

image

Добавим Constraints:
кнопке — центрирование по вертикали и горизонтали, а также Height и Wight.
трем View — Height и Wight, Horizontal space и Vertical Space. Тоже самое проделаем и с UILabel.

В итоге наш ViewController вместе с Constraints будет иметь следующий вид:

image

Запустим наше приложение на выполнение на iPhone 5 portrait и landscape:

image
image

и iPad 2:
image

Как видим результат не очень утешительный, на iPhone 5 portrait две вью вылезли непонятно куда, в landscape режиме с размерами вьюх и лейбла просто беда, а их ширина чересчур велика. В iPad все не так страшно, но расстояние между лейблом и кнопкой слишком большое, а первая UIView растянулась.

Сначала исправим все для iPhone 5. Для начала в Size Classes выберем (compact Width | regular Height) для портретной ориентации:

image

Обратите внимание, что полоска выбора Size Classes стала синей. Это говорит о том, что бы вы были повнимательней, так как на данный момент находитесь не в базовой конфигурации, и изменения 4х типов будут применимы только к данному типу и ориентации устройства. Какие же это изменения? Как было сказано, их 4:

1) значения Constraint'ов
2) шрифты
3) включение/выключение Constraint'ов
4) включение/выключение subviews

P.S. Напомним, все что будет далее сделано, применимо ТОЛЬКО к портретной ориентации iPhone 5, о чем нас информирует зеленая точка в сетке Size Classes!

Начнем с 1 пункта и исправим значения Constraint, что бы все выглядело корректно. В данной конфигурации мы видим ошибки Constraint:

image

Для их исправления в данной ориентации идем к настройкам Constraint, и выберем значение горизонтального расстояния между View так как для iPhone оно слишком большое:

image

В панели Utilities в вкладке Size Inspector возле пункта Constant (а также возле „Installed“, но о нем позже) жмем небольшой знак „+“:

image

И выбираем „Compact Width | Regular Height (current)“:

image

Видим, что у нас появилась новая Constant (wC hR), для данной ориентации, введем значение 34. Тоже самое проделываем со вторым Constraint между 2й и 3й вью.

image

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

image

Тоже самое проделаем и с шириной кнопки и лейбла для данной ориентации. Выберем их ширину, добавим Constant „Compact Width | Regular Height (current)“, и установим значение 200:

image

Как мы помним, нашей кнопке мы устанавливали Horizontal Center in Container и Vertical Center in Container, от чего и возникают оставшиеся конфликты. Их можно решить как минимум 2 способами в зависимости от того что требуется, 1й способ, разместить кнопку по центру как она того и требует, либо оставить все как есть и воспользоваться 3 пунктом из списка выше. В данном примере мы воспользуемся способом под номером 2, так как это все таки статья о Size Classes.

Итак, для того чтобы „выключить“ Constraint, которая нам мешает, нужно ее выбрать из списка:

image

И снова обратиться к вкладке Size Inspector, только теперь нажимаем „+“ который возле чекбокса „Installed“:

image

Опять таки выбираем и выбираем „Compact Width | Regular Height (current)“, и теперь у нас как и с Constant (wC hR) появился чекбокс wC hR „Installed“, убираем его и видим, что ошибки исчезли, а для данной конфигурации данный Constrain выключен:

image

Запустим наше приложение на выполнение и увидим, что все отображается корректно (ну почти все, так как размеры View не подгонялись для конкретного устройства Xcode автоматически уменьшает одну из них):

image

Данную проблему мы исправим позже.

Проделываем аналогичные действия для iPhone 5 landscape, выберем в сетке Size Classes (compact Width | compact Height):

image

Нам нужно так как и для портретного режима изменить расстояние между вью и размеры кнопки и лейбла, я выбрал такие же значения как и для портретного режима: расстояние между вью 34, размер кнопки и лейбла 200. Запустим наш апп:

image

Опять таки из за того, что мы не подгоняли размер, Xcode растягивает View. Исправим эту ошибку в портретной (compact Width | regular Height) и ландшафтной (compact Width | compact Height) ориентации. Выделим наши 3 вьюхи и выбираем в меню Editor -> Pin -> Widths Equally. После чего запустим наше приложение на выполнение и видим что все отображается корректно:

image
image

Дабы не повторяться для iPad, мы проделываем все тоже самое, что и для iPhone, только в сетке Size Classes выбираем regular Width | regular Height, что соответствует портретной и ландшафтной ориентации iPad. В данной ситуации мы просто обновили все Constraint, отцентрировали и выставили Width, Height Equally:

image
image

Осталось 2 пункта, по которым не прошлись, это включение/выключение subviews и шрифты. Выберем ландшафтный режим iPhone compact Width | compact Height. Выделим наш UILabel и перейдем в Attributes Inspector, напротив шрифта мы увидим уже знакомый нам „+“:

image

По аналогии с Constraint добавляем „compact Width | compact Height (current)“. Для значения wC hC выставим шрифт Helvetica Neue Ultra Light и размер 23. При запуске мы увидим что в портретной ориентации шрифт стандартный а уже в ландшафте Helvetica Neue:

image
image

Итак, последний пункт это включение и выключение subviews. Я думаю, тут объяснять уже ничего не надо, и так все становится понятно после всех этих разжовываний. Все по аналогии с включением выключением Constraint, разница лишь только в том, что тут мы выключим кнопку для конкретной ориентации:

image
image

В общем, моя первая статья получилась не маленькая, но и небольшая, надеюсь, она интересная и полезная. На момент написания статьи Xcode 6 еще в бете, и не выпущен iPhone 6, после презентации которого возможно пополнится документация, а также изменится и внешний вид ViewController'ов в storyboard, так как в данной ситуации не очень удобно все размещать в „квадратах“, хотя в сетке Size Classes выбран iPhone landscape.

По материалам: developer.apple.com/library/prerelease/ios/recipes/xcode_help-IB_adaptive_sizes/EnablingAdaptiveSizeDesign.html#//apple_ref/doc/uid/TP40014436-CH1-SW1

Спасибо за внимание!
Метки:
Поделиться публикацией
Похожие публикации
Комментарии 19
  • +1
    Думаю это многим пригодится!
    Благодарю за хорошую и полезную статью!

    P.S. Можно ли все это проделать в коде, не используя storyboard?
    • +1
      Можно и в коде.
      1. Есть протокол UITraitEnvironment и метод:
       traitCollectionDidChange:(UITraitCollection *)previousTraitCollection 

      который следит за изменениями текущего trait collection.

      2. А для изменения UITraitCollection вручную можно проделать следующее:

      
          UITraitCollection *horizontalSizeClass = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
          [self setOverrideTraitCollection:horizontalSizeClass forChildViewController:self.viewController];
      


      либо так:

      
          UITraitCollection *verticalSizeClass = [UITraitCollection traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassCompact];
          UITraitCollection *horizontalSizeClass = [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
          NSArray *traitCollectionArray = [NSArray arrayWithObjects:verticalSizeClass, horizontalSizeClass, nil];
          UITraitCollection *combinedTraits = [UITraitCollection traitCollectionWithTraitsFromCollections:traitCollectionArray];
          [self setOverrideTraitCollection:combinedTraits forChildViewController:self.viewController];
      
    • 0
      Я бы посоветовал еще юзать вторым окном Preview, чтобы было видно результаты изменений в реальном времени
      • 0
        Согласе, Preview очень полезная штука, тем более в Xcode 6 можно просматривать результат одновременно на всех семействах устройств, при чем с их же портретными ориентациями, что очень удобно!
        • 0
          Ну, я это и имел ввиду :) У вас не возникало проблем с программным созданием constraits в xcode 6?
          • 0
            Честно, пытаюсь не создавать программно constraits, только в крайнем случае. Все таки через storyboards визуально удобней :)
            • 0
              Ух, вас ждут новые открытия! :)
              Кстати, а почему примеры на obj-c? Почему не swift?
              • +1
                Для меня пока obj-c более родной, да и swift не очень нравится. Хотя от него уже не уйдешь :)
      • 0
        Статья полезная!
        В тексте закралась ошибка: а их ширина через чур велика
        • 0
          Спасибо! Исправил. Статью писал в конце рабочего дня, еще и в пятницу, по этому ошибки не исключены :)
        • 0
          Чувствую, теперь больше времени будет уходить на расстановку кнопочек, чем на написание кода.
          • 0
            Вместо того, чтобы упростить описание интерфейса путем переноса этого самого описания в декларативный код (xaml в качестве примера), Apple накрутила еще одну неочевидную сущность :( И так «верстать» iOS UI было тяжко, а стало еще запутанней. И не понять, толи это наследие, которое навсегда останется с разработчиком, толи оптимизация (тобиш потребуем от разработчика как можно больше данных, зато железке будет проще из переваривать), толи я чего-то не понимаю/не осилил. Но вот честно, мне проще ручками описать xml с версткой, чем работать с эпловским UI дизайнером. Может порекомендуете чего толкового почитать на эту тему? Или там дзен достигается лишь путем проб и ошибок?

            Автору за статью огромное спасибо. Познавательно и полезно.
            • 0
              Xaml, по крайне мере на десктопе очень мощная штука, к примеру, засунуть в button grid нет сложности, но из-за этого дикий overhead.
              Думаю, что Apple бьется за эффективное использование ресурсов. Смотрел сессию с WWDC 2014 где рассказывали про blur использованные в UX iOS7,8 (центр уведомлений, control center), приводили примеры как и почему это очень ресурсоёмкая штука. А т.к. blur-ы очень модная сейчас вещь, то представляю, как дизайнер может накрутить в xaml 2-3 плоскости с блюром перекрывающие друг друга, тут то fps у интерфейса и просядет, и аккумулятора побольше скушает.
            • 0
              Скажите, пожалуйста, как различить программно в какой у нас ориентации iPad c помощью UITraitCollection?
              • 0
                Именно с помощью UITraitCollection различить не получится, так как при его использовании HorizontalSizeClass и VerticalSizeClass будет == regular. В итоге, в какой ориентации б iPad не был, SizeClasses всегда будут regular. Более того при подключении протокола UITraitEnvironment и запуске приложения на iPad, метод:
                 - (void) traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
                

                при смене ориентации вызываться НЕ будет.
                • 0
                  Так вот я и думаю если надо разный лайаут в разных ориентациях ipad то что делать?
                  • 0
                    В интернете подсказывают два решения проблемы
                    1) ориентироваться на viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:
                    2) проверять в viewDidLayoutSubviews и обновлять layout
                    • 0
                      Спасибо. Первый способ выглядит привлекатльным, но как-то всё равно не то…
                      • 0
                        Ходят слухи, что это сделано специально для того, что бы дать возможно запускать на айпадах по несколько приложений одновременно в двухоконном режиме. в таком случае, привязка к ориентации выглядит действительно глупо. Но опять же, слухи… за что купил, за то и продал

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.