Лекция 6. Layout, @ViewBuilder. CS193P Spring 2023.

Ниже представлен фрагмент Лекции 6 Стэнфордского курса CS193P Весна 2023 «Разработка iOS приложений с помощью SwiftUI«.
Полный русскоязычный неавторизованный конспект Лекции 6 в формате Google Doc и в виде PDF-файла, который можно скачать и использовать offline, доступны на платной основе.
Код находится на GitHub.

С полным перечнем Лекций и Домашних Заданий на русском языке можно познакомиться здесь.

На сегодняшней Лекции 6, a, по сути, на всей этой неделе мы подберем несколько тем и попытаемся понять больше о том, как то, что мы делали, действительно работает.

Первая тема — это Layout, то есть расположение Views на экране. Как место на экране распределяется между всеми Views с помощью HStack, LazyVGrid, ScrollView и всех других подобных вещей? Как они решают, кому какое место достанется?
Затем у нас будет небольшая демонстрация.

Аналогичная ситуация с @ViewBuilder.
У нас есть есть такая классная штука как @ViewBuilder. Мы знаем, что это просто функция, которая возвращает some View. Но мы также знаем, что это круто и что это список Views, там есть if else, if let, switch и локальные переменные.
Так как же работает @ViewBuilder? Я не буду говорить о том, как на самом деле реализован @ViewBuilder, но я собираюсь поговорить о том, как вы его используете и как он работает.

Расположение на экране: Layout.

Как пространство на экране назначается и распределяется между всеми Views, которые там появляются? И это на самом деле это невероятно элегантная маленькая система и очень-очень простая, но в то же время с очень строгими правилами. Это делает расположение Views настолько невероятно предсказуемым, почти 100% предсказуемым.

Работа системы Layout состоит из 3-х шагов:

Первый шаг состоит, конечно, в том, что Views предоставляется некоторое пространство. Например, вашему ContentView, который находится в самом верху вашего приложения, доступен весь экран. Это отправная точка.
Но как только вы начнете создавать HStacks и VGrids, вам предлагают все меньше и меньше места по мере того, как вы спускаетесь вниз по иерархии Views.
Контейнерам Views, таким как HStack или VGrid или другим подобным им контейнерам, предлагается определенное пространство, a они в свою очередь предлагают его своим “детям”-Views с помощью определенных алгоритмов. 

Затем выполняется второй шаг, крайне важный и все дело именно в нём, он заставляет всю систему Layout действительно работать, потому что после того, как Views предложат пространство, даже если это обычный текст Text или изображение Image или что-то еще, ОНИ ВЫБИРАЮТ, какого размера они хотят быть. Единственный, кто может выбрать размер View — это само View. Невозможно принудительно указать размер View, ОНИ сами выбирают свой размер в зависимости от предложенного им пространства. Мы собираемся поговорить о том, как они это делают, но это НЕРУШИМОЕ ПРАВИЛО: Views ВЫБИРАЮТ свой собственный размер.
И именно это во многом делает систему Layout полностью предсказуемой, у вас не бывает странных случаев, когда вы действительно не понимаете, что произойдет. Никто никому ничего не навязывает.

Затем, на третьем шаге, как только Views выбрали свои размеры, они хотят вернуться в контейнер Views, который предлагал им пространство, и быть размещенными внутри него. 
Теперь, когда контейнеры Views знают все размеры, они могут размещать все вещи внутри себя, Например, HStack размещает Views, сдвигая их вправо или влево. То есть позиционирование Views полностью зависит от HStack.
Таким образом, размеры Views выбираются самими Views, а позиционирование осуществляется контейнерами Views.

Так работает система Layout. И это всегда работает таким образом, и у этого есть действительно хороший маленький протокол protocol Layout для исполнения этого «танца». Я покажу, как выглядит этот протокол, когда мы доберемся до демонстрационного примера.

Итак, давайте поговорим о некоторых из контейнерах Views, о том, как они принимают решения для выполнения всех этих шагов.

HStack и VStack

И основные из них — HStack и VStack — это самые простые и основные механизмы для расположение Views на экране среди всех контейнеров Views . Они четко распределяют пространство, которое им предлагается, между всеми Views внутри них. 

Ключом к HStack и VStack является идея того, насколько “гибким” является View внутри HStack с точки зрения использования пространства.
Некоторые Views — не очень “гибкие”, так как они в основном хотят определенного пространства. Дадим им это.
Другие Views — невероятно “гибкие”, например, прямоугольник с закругленными углами RoundedRectangle. Он может быть крошечным. Это может быть гигантский.
Но, конечно, есть Views — что-то среднее между очень-очень “гибким” View и чем-то менее “гибким”.

Давайте поговорим о некоторых примерах “гибкости”.
Например, у нас есть изображение Image (systemName:), это очень “негибкоеView, оно хочет быть фиксированного размер, равному размеру этого изображения, и не меньше, и не больше.
Наименее “гибким” View, вероятно, является изображение Image.

Еще одним довольно “негибкимView является текст Text.
Text всегда хочет “подогнать” свой размер к размеру строки, находящейся внутри него, но в крайнем случае, вы можете его немного уменьшить. Text позволяет себе быть меньше либо путем выполнения минимального масштабирования, помните, мы использовали .minimumScaleFactor(0.01), чтобы подогнать к нужному размеру, либо просто “обрезанием” текста и размещением … в конце. Так что Text — довольно “негибкие”, они упорно хотят соответствовать размеру своего текста, но могут немного и уменьшиться.

На другом конце спектра находится очень “гибкий” прямоугольник с закругленными углами RoundedRectangle и круг Circle, вообще все геометрические фигуры Shape стремятся быть полностью “гибкими”. Если вы предложите им немного пространства, то они возьмут все пространство целиком и заполнят его максимально.

Такая “гибкость” является ключом к тому, как работают HStack и VStack.
После предложения своего пространства первому наименее “гибкому” View, HStack предоставляет ему это пространство, вычитает его из пространства, которое у него есть, а затем с оставшейся частью пространства переходит к следующему наименее “гибкому” View.
Первыми занимают пространство наименее “гибкие” Views, а затем рассматривается следующий уровень “наименее гибких” Views, потом еще следующий, пока, надеюсь, не останется немного места, и тогда вы можете передать его “гибкому” View, если оно там есть.

Или вы можете стать настолько большим, что даже не сможете “подогнать” все Views, сказав, что они хотят больше пространства, чем у него есть.

И тогда HStack должен решить, что делать.
Он может вернуться к тому, что является немного “гибким” View, таким, как Text, и сказать, что на самом деле он собираюсь предложить им немного меньше пространства, так как оно у него закончилось. И это может быть принято или нет, чтобы HStack смог приспособиться.

Так что этот «танец» туда-сюда будет продолжаться до тех пор, пока HStack не сможет подогнать все Views или вообще не сможет их подогнать.

Например,вы пытаетесь заставить Views быть меньше, чем они, возможно, могут быть. Это похоже на то, что вы размещаете 100 изображений ImageViews в HStack, которое довольно маленькое. Они просто выйдут за пределы самого HStack.

Конечно, после того, как ВСЕ Views внутри стека выбрали свои собственные размеры, размеры самих HStack и VStack «подгоняются» к размерам этих Views. Это означает, что стек может быть меньше, чем пространство, которое ему изначально предлагалось, если все его Views — “негибкие” и занимают меньше места. Или больше, если стек не смог разместить их все.
Тогда HStack обнаруживает, что он слишком велик.

Ещё раз повторюсь, что HStack — это просто View и он может сам выбрать свой размер точно так же, как и все Views, что внутри него, и он может выбрать слишком большой размер, если не смог разместить там все Views.
Итак, это все. Вот как HStack работает, по крайней мере очень предсказуемо и разумно.

Система Layout (расположение Views на экране) — это одна из самых мощных и элегантныхчастей SwiftUI.

. . . . . . . . . . . . . .

LazyHStack и LazyVStack

Давайте поговорим о LazyHStack и LazyVStack.

“Lazy” в первую очередь означает, что на самом деле они не собираюсь размещать Views, которых нет на экране. Начинают размещать Views до тех пор, пока не обнаружат, что ушли за пределы экрана: “О, я за пределами своих границ, больше не буду размещать Views.”

Таким образом, у вас может быть тысяча вещей. Возможно, это все песни из вашей музыкальной библиотеки. Вы не хотите размещать сразу тысячу вещей, просто покажите первые семь. Как только я начну прокручивать, вы, ребята, начните размещать остальные. LazyHStack и LazyVStack делают именно это.

Что касается “Lazy”, то это не занимают все пространство внутри них, даже если внутри них есть “гибкие” Views, они не отводят им всё пространство. Они делают их как можно меньше. Этим они отличается от HStack от VStack, которые отдают всё дополнительное пространство “гибким” Views, если они у них есть.

“Lazy”— нет, так что вы почти всегда будете помещать их внутри ScrollView, потому что они будут уменьшаться до размера вещей внутри них. Обычно вы не размещаете в них “гибкие” Views, если только у вас есть инструменты, которые ограничивают “гибкость”,такие, например,как соотношение сторон aspeсtRation или чего-то еще, что заставляет их быть реального размера.

А еще есть LazyHGrid и LazyVGrid.

LazyHGrid и LazyVGrid

Мы это уже видели, когда использовали LazyVGrid для размещения карт в нашей игре Memorize. Я правда не собираюсь здесь слишком много об этом говорить, но они похожи на LazyHStack и LazyVGrid в том, что они будут уменьшаются до размера вещей внутри них. Так что обычно вы помещаете их в ScrollView. Вы уже знаете, как этим пользоваться.

. . . . . . . . . . . . . .

Это два небольших фрагмента Лекции 6.
Далее на Лекции 6 рассматриваются следующие вопросы:

  • Spacer () и Divider ()
  • Приоритет размещения .layoutPriority
  • Выравнивание alignment
  • Grid
  • ScrollView. ViewThatFits, Form, List, OutlineGroup и DisclosureGroup
  • ZStack, модификаторы .background и .overlay
  • Модификаторы View
  • GeometryReader
  • Safe Area (Безопасная область)
  • Возвращаемся к демонстрационному примеру. GeometryReader
  • .background как отладочный инструмент
  • Выбор размера карты gridItemWidthThatFits
  • @ViewBuilder
  • Возвращаемся к демонстрационному примеру. @ViewBuilder
  • AspectVGrid
  • Generic Item: Identifiable
  • Generic ItemView: View
  • @ViewBuilder в демо

Полный русскоязычный неавторизованный конспект Лекции 6 в формате Google Doc и в виде PDF-файла, который можно скачать и использовать offline, доступны на платной основе.
Код находится на GitHub.

С полным перечнем Лекций и Домашних Заданий на русском языке можно познакомиться здесь.