Auto Layout и отрисовка
Intrinsic content size и приоритеты ограничений, отладка конфликтов constraint'ов, саморазмерящиеся ячейки и что ломает отрисовку при прокрутке (offscreen-проходы).
9 вопросов
JuniorТеорияОчень частоЧто такое intrinsic content size, у каких view он есть и что происходит с view без него?
Что такое intrinsic content size, у каких view он есть и что происходит с view без него?
Intrinsic content size — это размер, который view выводит из своего содержимого: UILabel из текста, UIButton из заголовка. У обычного UIView его нет, поэтому без constraint'ов на размер он остаётся неоднозначным.
Типичные ошибки
- ✗Считают, что любой view, включая обычный
UIView, сообщает intrinsic-размер - ✗Думают, что view без intrinsic-размера сам задаёт размер по сабвью
- ✗Путают intrinsic content size с frame, который даёт родитель
Уточняющие вопросы
- →Какое значение возвращает view, когда на оси нет intrinsic-размера?
- →Как content hugging и compression resistance действуют на intrinsic-размер?
SeniorДизайнОчень частоСпроектируйте экран UIKit, который адаптируется под iPhone, iPad и iPad Split View без отдельной раскладки на каждое устройство. Он перестраивает основную область контента и вторичную панель деталей — рядом, когда ширины хватает, и стопкой, когда нет. Ограничения — никаких зашитых проверок устройства или магических значений в точках, один и тот же код view controller обслуживает любой размер, а текст и контролы уважают Dynamic Type и safe area. Объясните, как вы задаёте раскладку через адаптивный API UIKit (классы размеров size classes и trait collection) плюс layout guides, как переключаетесь между формой рядом и стопкой и что намеренно не зашиваете.
Спроектируйте экран UIKit, который адаптируется под iPhone, iPad и iPad Split View без отдельной раскладки на каждое устройство. Он перестраивает основную область контента и вторичную панель деталей — рядом, когда ширины хватает, и стопкой, когда нет. Ограничения — никаких зашитых проверок устройства или магических значений в точках, один и тот же код view controller обслуживает любой размер, а текст и контролы уважают Dynamic Type и safe area. Объясните, как вы задаёте раскладку через адаптивный API UIKit (классы размеров size classes и trait collection) плюс layout guides, как переключаетесь между формой рядом и стопкой и что намеренно не зашиваете.
Стройте раскладку от horizontal size class в trait collection, а не от модели устройства — regular width ставит контент и детали рядом, compact — стопкой. Переключайте наборы constraint'ов в traitCollectionDidChange. Крепите к safeAreaLayoutGuide и readableContentGuide, текст отдайте Dynamic Type и не зашивайте проверок устройства.
Типичные ошибки
- ✗Ветвятся по idiom устройства вместо horizontal size class
- ✗Игнорируют Split View, где iPad-приложение идёт в compact-ширине
- ✗Крепят к сырым bounds вместо safe-area и readable-content guide
Уточняющие вопросы
- →Как анимировать переход, когда size class меняется в середине сессии?
- →Почему Split View делает ветвление по idiom устройства ненадёжным на iPad?
JuniorТеорияЧастоЧто выражает constraint в Auto Layout и что делает раскладку неоднозначной?
Что выражает constraint в Auto Layout и что делает раскладку неоднозначной?
Constraint — это линейное уравнение attr1 = m × attr2 + c с приоритетом; движок решает всю систему для frame каждого view. Раскладка неоднозначна, когда недоопределена — constraint'ов мало, чтобы задать единственный размер и позицию.
Типичные ошибки
- ✗Путают неоднозначную (недоопределённую) раскладку с неудовлетворимой (переопределённой)
- ✗Думают, что constraint хранит абсолютный frame, а не отношение
- ✗Забывают, что у constraint есть приоритет, а не только равенство
Уточняющие вопросы
- →Как приоритет constraint меняет то, какой из них движок сломает первым?
- →Как во время выполнения получить список неоднозначно размещённых view?
MiddleДебаггингЧастоЧтение лога «Unable to simultaneously satisfy constraints»
Чтение лога «Unable to simultaneously satisfy constraints»
Лог перечисляет конфликтующие constraint'ы и называет сломанный. Задайте каждому .identifier, чтобы связать адреса, и найдите переопределённую ось — ширину 200 pt против leading и trailing. Уберите один или понизьте приоритет.
Типичные ошибки
- ✗Игнорируют предупреждение, потому что приложение всё же что-то рисует
- ✗Удаляют первый constraint из списка, не найдя настоящий конфликт
- ✗Оставляют constraint'ы безымянными, и адреса не связать с кодом
Уточняющие вопросы
- →Чем помогает symbolic breakpoint на assertion раскладки поймать это вживую?
- →Почему понижение приоритета решает конфликт лучше удаления constraint?
MiddleТеорияЧастоДве метки в ряд: какая обрежется и как это решают hugging и compression resistance?
Две метки в ряд: какая обрежется и как это решают hugging и compression resistance?
Compression resistance — насколько сильно view сопротивляется сжатию ниже intrinsic-размера; content hugging — росту выше него. Когда две метки не помещаются в ряд, обрежется та, у которой ниже приоритет compression resistance — поднимите его у метки, которая должна остаться целой.
Типичные ошибки
- ✗Меняют местами роли hugging и compression resistance
- ✗Думают, что обрезание решает порядок вставки, а не приоритет
- ✗Считают, что обрезанием управляет число символов, а не приоритет
Уточняющие вопросы
- →Что решает content hugging, когда в ряду лишнее место, а не нехватка?
- →Почему держать приоритеты меток разными, а не равными?
JuniorПроизводительностьИногдаПочему глубоко вложенная иерархия Auto Layout в ячейке роняет кадры и когда переходить на ручную раскладку?
Почему глубоко вложенная иерархия Auto Layout в ячейке роняет кадры и когда переходить на ручную раскладку?
Стоимость решателя растёт хуже линейного от числа constraint'ов, а глубокая иерархия решает всю систему на каждом проходе в главном потоке. Скролл превышает бюджет кадра. Уплощайте иерархию или берите ручной layoutSubviews.
Типичные ошибки
- ✗Считают стоимость решателя линейной, а не суперлинейной по числу constraint'ов
- ✗Думают, что раскладка идёт вне главного потока и на кадры не влияет
- ✗Снижают качество картинок раньше, чем упрощают граф constraint'ов
Уточняющие вопросы
- →Как переиспользование ячеек связано со стоимостью пересчёта на проходе?
- →Какие сигналы в Instruments указывают на раскладку, а не на отрисовку?
JuniorПроизводительностьИногдаЧто запускает offscreen-проход при прокрутке и как его избежать?
Что запускает offscreen-проход при прокрутке и как его избежать?
Offscreen-рендеринг делает проход в буфер перед композитингом — дорого при скролле. cornerRadius с masksToBounds, тень без shadowPath, маски и blur запускают его. Задайте shadowPath, скругляйте картинки или растеризуйте статичные слои.
Типичные ошибки
- ✗Думают, что offscreen-рендеринг — это про view вне экрана
- ✗Оставляют тени без
shadowPathи платят проход из альфа-канала - ✗Считают, что одна непрозрачность убирает проходы скругления и теней
Уточняющие вопросы
- →Почему явный
shadowPathубирает offscreen-проход, который стоила бы тень? - →Когда
shouldRasterize— выигрыш, а не дополнительная цена?
JuniorПроизводительностьИногдаЧто делает estimatedRowHeight и почему индикатор прокрутки прыгает во время скролла?
Что делает estimatedRowHeight и почему индикатор прокрутки прыгает во время скролла?
estimatedRowHeight даёт таблице приблизительную высоту, чтобы задать scroll-контент, не измеряя все ячейки заранее; настоящие высоты считаются лениво по мере появления. Индикатор прыгает, потому что каждая уточнённая оценка меняет общую высоту контента.
Типичные ошибки
- ✗Думают, что оценка — это финальная высота, а не заглушка
- ✗Считают, что все реальные высоты вычисляются сразу, а не лениво
- ✗Винят в прыжке баг, а не коррекцию высоты контента
Уточняющие вопросы
- →Что будет со скроллом, если оценка сильно расходится с реальными высотами?
- →Почему хорошая оценка уменьшает заметные прыжки?
MiddleДебаггингИногдаСаморазмерящаяся ячейка отрисовывается с неверной высотой при первой раскладке
Саморазмерящаяся ячейка отрисовывается с неверной высотой при первой раскладке
title закреплён сверху, слева и справа, но не к contentView.bottomAnchor, поэтому вертикальная цепочка не доходит до низа и высота ячейки неоднозначна, оставаясь на оценке. Добавьте недостающий нижний constraint, чтобы высота шла из содержимого.
Типичные ошибки
- ✗Оставляют разрыв в цепочке constraint'ов от верха до низа contentView
- ✗Принимают оценку за финальную высоту, а не за заглушку
- ✗Винят тайминг reload вместо неоднозначных вертикальных constraint'ов
Уточняющие вопросы
- →Почему цепочка должна доходить именно до
contentView.bottomAnchor, а не ячейки? - →Как
numberOfLines = 0у многострочной метки взаимодействует с этой цепочкой?