На мой взгляд, инструменты верстки UI в iOS далеко не идеальны и обладают высоким порогом вхождения (в отличии, например, от html). Будучи начинающим iOS разработчиком, я тратил огромное количество времени на попытки понять принципы работы constraints, циклы layout-a и т.д. Autoresizing — один из инструментов, который помогает iOS разработчику покрыть часть простых кейсов, связанных с версткой. Цель данной статьи — поближе познакомиться с этим инструментом.
Способы верстки в iOS
На сегодняшний день UIKit предоставляет разработчикам 2 способа управления версткой: ручной (или по-другому верстка на фреймах) и Autolayout.
Под ручной версткой понимается управление размерами и позицией view вручную, путем изменения свойств frame (либо center и bounds). Как правило, данный способ реализуется при помощи переопределения метода layoutSubviews UIView, на момент вызова которого, нам известно свойство bounds текущего экземпляра. В этом методе мы вручную определяем позицию и размеры дочерних вью.
Autolayout использует набор ограничений (constraints), которые описывают, как будут располагаться вью относительно друг друга. В отличие от ручного способа, здесь нет необходимости высчитывать frame каждый раз, когда в нашей верстке что-то изменилось, Autolayout сделает это за нас. Данный способ также допускает ручную настройку frame в случае, если мы хотим подкорректировать результат лейаута. Для этого нужно переопределить layoutSubviews, вызывать родительскую реализацию этого метода (здесь дочерние вью получат свои размеры из движка Autolayout), скорректировать фреймы дочерних вью.
Каждый из способов обладает своими преимуществами и недостатками, описание которых достойно отдельной статьи, мы же остановим наше внимание на Autoresizing.
Основные принципы Autoresizing
Autoresizing работает всегда, когда мы решили использовать ручную верстку (и в некоторых случаях при использовании Autolayout, но об этом чуть позже). Autoresizing основан на двух основных понятиях: springs (пружины) и struts (распорки). Перевод этих терминов точно отражает их смысл: springs — что-то, что растягивается, struts — что-то, что всегда имеет фиксированный размер. Данные понятия применимы к следующим параметрам разметки:
1) Внутренние — Width, Height. Описывают размер вью.
2) Внешние — Left, Right, Top, Bottom. Описывают положение вью внутри родителя — отступ от левого, правого, верхнего и нижнего края соответственно.
Рассмотрим простой пример того, как это работает. Допустим, у нас есть задача: необходимо, чтобы дочерняя вью всегда располагалась по центру родительской и пропорционально изменяла размер при изменении размера родителя (Рис. 1). Решением в данном случае будет установить struts для всех внешних параметров, и springs для всех внутренних параметров.
Другой пример: необходимо разместить вью с фиксированным размером в правом нижнем углу родительской вью (Рис. 2). Для это необходимо установить struts для параметров Right и Bottom, а также для всех внутренних параметров, т.к. расстояния от нижнего и правого края до родителя и размер нашей вью строго зафиксированы. Также нужно установить springs для Left и Top, т.к. эти параметры будут изменяться при изменении размеров родительской вью.
Autoresizing в коде
Единственный способ регулировать Autoresizing в коде — свойство autoresizingMask у UIView. Данное свойство имеет тип OptionSet со следующим набором опций:
- flexibleLeftMargin
- flexibleWidth
- flexibleRightMargin
- flexibleTopMargin
- flexibleHeight
- flexibleBottomMargin
Как данное свойство соотносится со stings и struts, внешними и внутренними параметрами разметки? Каждая опция в autoresizingMask — это springs для определенного параметра, а отсутствие опции — автоматически означает struts.
Теперь попробуем решить первую задачу с центрированием дочерней вью (Рис. 1). Для этого нам необходимо задать свойство autoresizingMask, как показано на фрагменте кода ниже.
// Создаем родительскую view
let v1 = UIView(frame: CGRect(x: 100, y: 250, width: 200, height: 200))
v1.backgroundColor = .blue
// Создаем дочернюю вью
let v2 = UIView(frame: v1.bounds.insetBy(dx: 30, dy: 30))
v2.backgroundColor = .red
// Добавление в иерархию
self.view.addSubview(v1)
v1.addSubview(v2)
// Устанавливаем autoresizing
v2.autoresizingMask = [.flexibleWidth, .flexibleHeight]
Установив значение свойства [.flexibleWidth, .flexibleHeight], мы говорим, что хотим сделать изменяемые значения для параметров width и height (springs), а для остальных свойств оставить фиксированные значения (struts).
Вторая задача решается при помощи задания значения [.flexibleTopMargin, .flexibleLeftMargin]. Мы не указали в опциях размер, а также нижний и правый отступ, а значит, эти параметры будут оставаться постоянными (Рис. 2).
По умолчанию свойство autoresizingMasks является пустым. Что произойдет, если оставить значение по умолчанию? Дочерняя вью будет всегда сохранять фиксированные отступы сверху и слева, а также фиксированный размер, при этом расстояние снизу и справа будут меняться. Это означает, что пустой autoresizingMasks равносилен [.flexibleBottomMargin, .flexibleRightMargin].
Autoresizing в Interface Builder
Interface Builder также позволяет конфигурировать autoresizingMasks. Для этого необходимо перейти во вкладку Size Inspectors, редактор Autoresizing выглядит как показано на рисунке ниже.
Здесь активные внешние параметры означают struts, неактивные — springs. Для внутренних параметров все ровно наоборот.
translatesAutoresizingMaskIntoConstraints
Вью начинает быть задействованной в расчете на Autolayout как только мы добавили ей или одной из ее дочерних вью constraint. В таком случае, если задать constraints только для дочерней вью, но не задавать для родительской, мы можем получить предупреждение, которое сообщает о том, что для родительской вью Autolayout не смог определить размер и позицию ("Position and size are ambiguous"). Такая ситуация могла возникнуть когда Autolayout только появился в iOS — новые вью верстались с использованием constraints, а старые оставались на ручном лейауте (он же Autoresizing, как мы выяснили ранее). Для того, чтобы избежать этого, разработчики Apple ввели свойство translatesAutoresizingMaskIntoConstraints. Если свойство имеет значение true, то для вью будут автоматически созданы constraints, которые однозначно определяют позицию и размер. Во View Hierarchy мы можем увидеть автоматически сгенерированные constraints.
Значение false отключает эту магию. При создании вью из кода — значение свойства — true, при создании из xib/storyboard — false. В Interface Builder свойство регулируется при помощи флага “Use Auto Layout", которое является инвертированным значением translatesAutoresizingMaskIntoConstraints.
Очень часто возникает ситуация, когда мы хотим создать нашу вью из кода и сверстать ее на Autolayout, т.е. явно задать ей constraints. Чтобы наши и автоматически сгенерированные constraints не конфликтовали, необходимо задать значение translatesAutoresizingMaskIntoConstraints в false.
Заключение
Очевидно, что с приходом Autolayout механизм Autoresizng утратил актуальность. Однако, его понимание необходимо как минимум, чтобы осознанно избегать конфликтов c constraints. Также данных механизм может быть полезен для простой верстки вью относительно родителя.