Это продолжение Лекции 1. Начало работы со SwiftUI. CS193P Spring 2021.
Начало Лекции 1 можно посмотреть здесь.
Мы вернемся к синтаксису some View через секунду и поймем, что на самом деле это означает. Но сначала я хочу поговорить об этом небольшом фрагменте кода:
Что это такое? Это просто код, который следует сразу после переменной var body: some View. На самом деле это функция, и я сказал вам, что Swift — это язык функционального программирования, а в языках функционального программирования функции действительно очень важны.
Фактически, функции в языках функционального программирования являются гражданами первого класса, и мы можем использовать функции где угодно.
Возможно, для некоторых из вас, кто программирует на Java или на подобных языках, иметь возможность разместить функции прямо посреди вашего кода, покажется неуместным и будет немного раздражать, но вы быстро к этому привыкните, потому что в языках функционального программирования функции есть везде. Они просто абсолютно везде.
Так получилось, что наша функция не принимает аргументов и у неё нет названия. Не нужно имя функции, потому что я только что разместил ее прямо в коде. Эта функция действительно что-то возвращает. Она возвращает текст Text («Hello, world!»).
Вы можете возразить: «Ну, а где же предложение return?» И действительно, там есть предложение return.
Это своего рода скрытое предложение return. Я собираюсь оставить его там, и если я выполню компиляцию и сборку кода с помощью команды Command-B, кстати, именно так компилируется код, то нам сообщат: “Build Succeeded” («Компиляция и сборка выполнены успешно»).
Предложение return принадлежит этому коду, просто Swift скрывается его, чтобы заставить мир выглядеть немного лучше. Мы увидим много примеров, когда Swift скрывает посторонние ключевые слова и вещи, когда и так очевидно, что там происходит. Он делает это постоянно, чтобы наш код выглядел немного лучше.
Наша функция без имя, она не принимает аргументов и возвращает текст Text («Hello, world!»). Так что он там делает? Что это за функция разместилась у нас прямо в середине нашего кода, сразу же после переменной var body: some View?
Фактически, переменная var body: some View не хранится в памяти. Это не переменная, хранящаяся в памяти. Это вычисляемая переменная, которая рассчитывается путем выполнения этой функцию. Каждый раз, когда кто-то запрашивает структуру struct ContentView, он запрашивает: “Эй, каково значение твоей переменной var body?», выполняется эта функция и возвращается все, что возвращает эта функция.
В нашем случае вернется этот “кирпичик” Lego (Лего), этот текст Text («Hello, world!»). А что собой представляет текст Text? Text — это еще одна структура struct, которая “ведет себя” как View, как “кирпичик” Lego (Лего), так и должно быть. Другими словами, где-то в SwiftUI есть структура struct Text: View и она “ведет себя” как View и, вероятно, у этой структуры есть переменная var body: some View, а внутри этой переменной есть какой-то код.
Может, не совсем так, но что-то в этом роде. Так что текст Text сам по себе “ведет себя” как View.
Итак, у нас здесь есть функция, которая возвращает нам то, что “ведет себя” как View, и это хорошо, потому что предполагается, что переменная var body имеет ТИП : some View, то есть является чем-то, что “ведет себя” как View. Так что переменная var body и возвращаемое функцией значение совпадают по ТИПу. Эта функция возвращает то, что соответствует ТИПу этой переменной. Конечно, если вы собираетесь устанавливать значение вычисляемой переменной с помощью таким образом заданной функции, то ТИПы вычисляемой переменной и возвращаемое значение функции должны соответствовать друг другу.
В нашем случае так оно и есть. Здорово. На самом деле это помогает нам понять, во что действительно превращается some View, когда компилятор компилирует этот код. some View — это не совсем ТИП. Это больше похоже на некоторый совет компилятору: «Эй, ТИП этой переменной будет some View. Пожалуйста, определи, каким он является на самом деле, и замените его этим ТИПом при компиляции «.
В нашем случае компилятор посмотрит на эту функцию, увидит, что она возвращает текст Text, и заменит some View на текст Text, потому что это и есть его ТИП.
И снова, если я скомпилирую этот код, все получится!
Поскольку переменная var body имеет ТИП Text. У нее есть функция, возвращающая текст Text. Почему мы с самого начала просто не можем сказать, что переменная var body имеет ТИП Text? Почему мы должны использовать этот странный ТИП some View?
Для этого есть две причины.
Один причина заключается в том, что мы, структура struct ContentView, пытается “вести себя” как View, но она как-то должна уметь сказать, что вы должны делать, чтобы “вести себя” как View. Вы же не можете сказать: «О, для того, чтобы ты “вел себя” как View, у тебя должна быть переменная var body, которая имеет ТИП Text «.
Потому что, конечно: ты хочешь “вести себя” как View, так, чтобы твоя переменная var body могла быть мощным View-объединителем (View combiner), который и создает весь ваш UI. Весь полный UI в этой версии приложения возвращается с помощью ContentView.
Часть кода ContentView — это просто декларация, просто объявление того, что здесь происходит. Но другую его часть кода мы собираемся сделать все сложнее и сложнее. Он начинается как один “кирпичик” Lego (Лего) в виде текста Text («Hello, world!»), а в конечном итоге становится целым игровым приложением.
По мере того, как код становится все более и более сложным, ТИП того, что возвращает ContentView, очевидно, будет становиться намного сложнее, и мы не хотим выяснить, что именно он собой представляет или печатать его.
Мы хотим, чтобы КОМПИЛЯТОР выяснил это для нас. Вот чем хороши компиляторы, которые, глядя на код, логически выводят, какой ТИП имеют те или иные вещи. У них это очень, очень хорошо получается.
Я могу показать вам это очень, очень ярко, говоря о том, что мы уже видели, то есть об Отступах .padding(). Я кликаю на текста Text («Hello, world!»), и это вернуло мне предварительный просмотр Preview и инспектор Inspector:
Я могу возобновить с помощью кнопки “Resume” предварительный просмотр Preview. Если вы помните, когда мы начинали работать с этим текстом Text («Hello, world!»), у нас были включены Отступы .padding(), и мы выключили их с помощью маленькая синей точки в инспекторе Inspector в разделе Отступы (Padding).
Я собираюсь снова его включить. Итак, наши Отступы (Padding) со всех сторон вернулись, и появился .padding() прямо в коде. Давай поговорим немного о том, что здесь произошло. Возможно, будет немного более понятнее, если я перенесу .padding() на ту же строку, что и Text («Hello, world!»), и мы посмотрим на это вот так.
Во-первых, позвольте мне объяснить, что padding() — это просто функция, которая существует во всех структурах struct, которые “ведут себя” как View. Любая структура struct вроде ContentView или Text, всё, что “ведет себя” как View, имеют эту функцию с именем padding().
Вот как вы вызываете функцию в Swift. Вы просто находите структуру struct, для которой хотите вызвать функцию. Если вы занимаетесь объектно-ориентированным программирование на Swift, тогда вы находите объект, то есть экземпляр класса class. Тогда вы просто ставите “точку” «.» и имя функции. Надеюсь, это очень знакомо вам из языка программирования Java и других языков. В Swift вы делаете это точно так же, а затем, если есть аргументы, вы предоставляете их в круглых скобках.
Вы обнаружите, что в Swift аргументы в функциях являются очень, очень гибкими. Вы можете задавать их по умолчанию. У вас могут быть разные аргументы, что означает разные вещи. Например, в функции padding() у нас есть аргумент .all:
… и позже я объясню, что такие вещи, означают, но в нашем случае это означает, что включены Отступы (Padding) со всех сторон. Вы видели, что если я отключу некоторые из них, то теперь у нас только горизонтальные Отступы (Padding):
Но есть и другие значения для аргумента функции padding(). Я могу ввести здесь число, например, написать padding (20), и это означает задать Отступ в 20 points со всех сторон:
Или Отступ padding (100) в 100 points со всех сторон:
Или вообще без аргументов padding (), что означает используйте значения для Отступа по умолчанию:
Это вариант Отступа хорош тем, что он разный на разных устройствах Apple: Apple Watch по сравнению с Apple TV по сравнению с MacOS по сравнению с iPad. Определенно вам вам понравится использовать значения аргументов по умолчанию, потому что они зависят от особенностей платформы.
Но дело в том, что мы можем использовать множество значений в аргументах, и это будет все та же функция padding () и делает она то же самое, просто вы предоставляете ей больше различной информации, чтобы точно сказать ей, что она должна делать.
Функция padding () что-то возвращает. И очень важно понимать, что же функция padding () возвращает. Она возвращает что-то, что “ведет себя” как View, но это нечто другое, чем Text, функция padding () НЕ возвращает Text. Она возвращает измененный другой View с измененными Отступами. Этим измененным View в данном случае является текст Text.
Если бы я захотел напечатать действительный ТИП, который возвращает padding (), то это было бы что-то наподобие ModifiedContent<>, что я не знаю точно:
Я мог бы посмотреть в документации, это что-то сложное, но это определенно не текст Text.
Фактически, если я сейчас поставлю вместо some View текст Text и выполню команду “Build” (Command-B), то мы получим сообщение написано «Build Failed» (“Компиляция не прошла.”).
Невозможно преобразовать выражение “some View в ТИП Text”, так как это выражение Text («Hello, world!»).padding() не является текстом Text, возвращается что-то вроде текста Text с Отступами padding(). Действительно очень важно понимать, что здесь происходит, потому что мы собираемся вызывать огромное множество маленьких функций вроде этой, которые возвращают новое View, отличное от другого первоначального View.
Такие функции называются модификаторами View, потому что они действительно модифицируют какой-нибудь другой View. В мире Lego (Лего) это можно представить как, если бы мы взяли этот “кирпич”, отправили его обратно на завод и сказали: «Пришлите нам новый, пожалуйста, это немного побит по краям «. По сути, это и произошло, и завод быстро бы отправил нам новый, а уже этот новый “кирпич”, а уже этот новый “кирпич” мы здесь используем.
Так что Text («Hello, world!»).padding() — это измененный (модифицированный) текст Text. Каждый раз, когда мы применяем здесь модификатор View, по существу, создается новый View. Вот почему, действительно ценно для нас иметь some View, особенно, когда наше View становится все сложнее и сложнее.
Мы можем продолжать добавлять модификаторы к нашему View, потому что мы, конечно, можем модифицировать View снова и снова. Например, возьмем наш текст Text («Hello, world!»).padding() и прямо в инспекторе Inspector изменить его цвет. Скажем, я изменю цвет шрифта текста на оранжевый.
Он добавил сюда еще один модификатор между Text («Hello, world!») и padding(). Посмотрим, сработало ли это и кликнем на кнопке “Resume” (“Возобновить”). Да, конечно, все работает.
Теперь у нас действительно есть три Views:
У нас есть оригинальный View.
У нас есть View, модифицированный функцией foregroundColor(Color.orange), который представляет собой текст Text с измененным цветом.
А затем у нас есть View, которое возвращается, взяв текст Text с измененным цветом и дополнив его отступами padding().
И именно этот View возвращается как some View.
Вы видите, что здесь много возможностей создания именно того View, которое мы хотим с помощью этих модификаторов, но на самом деле мы всего лишь говорим о том, чтобы взять “кирпич” и доработать его. Конечно, отдельный доработанный “кирпичик” Lego (Лего) это, конечно, замечательно, но нам нужно уметь комбинировать “кирпичи”, если мы создаем карточки, как изображено ниже, которые являются комбинацией (объединением) прямоугольника с закругленными углами и небольшого эмоджи (смайлика) поверх его.
Мы хотим объединять карточки в эту сетку, а затем объединить все это в большое приложение, Вот, что мы хотим сделать.
Давайте сосредоточимся здесь на том, чтобы создать только одну карточки с помощью View-объединителя (View combiner) для объединения прямоугольника с закругленными углами и с текстом Text поверх него в виде эмоджи (смайлика). Для этого я прокомментирую мой текст Text («Hello, world!») с помощью Command/. Между прочим, это отличный способ быстро закомментировать код.
Вы заметите, что у меня есть всевозможные ошибки и мой предварительный просмотр Preview не может быть быть построен и все такое. Это потому, что я закомментировал единственную строку в этой функции, которая возвращает some View. Это переменная var body :
А это функция, которая предоставляет значение этой переменной и должна вернуть some View.
Давайте сделаем это. Пусть функция вернет прямоугольник с закругленными углами RoundedRectangle.
RoundedRectangle — это ещё одна вещь, встроенная в SwiftUI, это “кирпичик” Lego (Лего).
Обратите внимание, что по мере ввода названия “кирпичика” Xcode прыгает туда-сюда, пытаюсь помочь вам. Здесь Xcode реально мне помогает с выбором разных способов создания прямоугольника с закругленными углами RoundedRectangle. Я могу это сделать, указав радиус закругления угла cornerRadius или даже точный размер cornerSize, то есть ширину и высоту закругленных углов.
Я хочу создать RoundedRectangle, указав радиус закругления угла cornerRadius, так что я собираюсь щелкнуть на верхней строке и, конечно же, Xcode проставляет значение радиус закругления по умолчанию, равное в нашем случае 25.
И мы получили прямоугольник RoundedRectangle с радиусом закругления углов 25. Он красиво выглядит, хорошо смотрится. Но случилось что-то несколько странное в процессе создания прямоугольника RoundedRectangle, чего вы раньше не видели. Вот как мы создали прямоугольник RoundedRectangle:
Посмотрите на аргумент RoundedRectangle, он сильно отличается от того, что был у текста Text («Hello, world!») или даже у вызовов этих функций foregroundColor(Color.orange) и padding():
У аргумента RoundedRectangle есть метка cornerRadius:
Swift не любит заставлять печатать то же самое снова и снова: Color, Color, Color.
Но в случае с прямоугольником RoundedRectangle я могу создать один прямоугольник RoundedRectangle, указав угол закругления cornerRadius, а другой — указав размер cornerSize, Если бы я просто написал RoundedRectangle (25.0), то было бы непонятно, что означает 25.0. Поэтому всякий раз, когда вы определяете свою собственную функцию и аргументы, и не очевидно, какими будет аргументы, вы захотите обозначить их меткой.
Мы будем говорить о функциях на следующей неделе, о том, как мы объявляем их, как даем им аргументы, как делаем эти аргументы необязательными, все эти дела. И мы увидим, что почти всегда мы используем метки для аргументов. Это действительно упрощает чтение кода. Это одна из лучших вещей, которые Swift унаследовал от своего предшественника, языка программирования Objective-C, это эти прекрасные метки.
Что касается прямоугольника RoundedRectangle, то в нем я могу изменить все, что я хочу, я также могу изменить это в инспекторе Inspector.
У нас есть прямоугольник с закругленными углами RoundedRectangle.
RoundedRectangle — это просто View, он “ведет себя” как View. Это “кирпичик” Lego (Лего). Так что с ним я могу делать такие вещи, как установка Отступов с помощью padding():
Может быть, мне нужен только горизонтальный Отступ — padding(.horizontal), и он будет делать то, что я хочу.
Все это отражается в инспекторе Inspector. Там все здорово.
Прямоугольник с закругленными углами RoundedRectangle также является геометрической фигурой Shape. Я говорил вам, что геометрические фигуры Shape — это “кирпичики” Lego (Лего). Круги Circle, прямоугольники с закругленными углами RoundedRectangle, овалы Oval, все они являются геометрическими фигурами Shape.
У геометрической фигуры Shape есть еще одна дополнительная вещь, которую может делать только она в дополнение к закрашиванию геометрической фигуры определенным цветом, как показано в нашем предварительном просмотре Preview. Геометрическая фигура Shape может очерчивать себя, и делается это с помощью .stroke (). Если мы в конце какой-то геометрической фигуры поставим .stroke (), то она просто покажет внешний край этой Shape.
Видите? Снаружи только внешний край. Это именно то, что мы хотим иметь, когда в нашей игре Memorize карты открываютcя на лицевой стороне:
Мы хотим, чтобы карта была закрашена, когда она лежит “лицом” вниз, и очерчена (обведена), когда она лежит “лицевой” стороной вверх. Вы также можете также закрасить геометрическую фигуру Shape с помощью .fill ().
… но это и так работает по умолчанию, вот почему у нас изначально закрашивание уже было, хотя в коде его вообще не было.
Функцию stroke () “понимает” только геометрическая фигура Shape. Поэтому я не могу использовать stroke () для текста Text, так как Text — это View, а не Shape, и его нельзя очерчивать. Но в то же время все геометрические фигуры Shape являются View, и поэтому мы можем использовать для них Отступы padding ().
Функцию stroke () также может иметь аргументы, которые не являются обязательными и имеют значения по умолчанию. Например, я могу задать ширину линии очерчивания stroke (lineWidth: 3), это делает линию очерчивания немного толще.
Обратите внимание, что у этого аргумента также есть метка lineWidth, так что метки используются не просто при создании структур struct, они также используются при вызове функций. Конечно, не у всех функций есть метки, например, padding (.horizontal) не использует метку, также мы видели, что foregroundColor(.orange) не использует метку, но некоторые из функций их имеют. Мы оставим в коде ту ширину линии обводки lineWidth: 3, которая там установлена просто для напоминания.
Мы можем также задать для нашего прямоугольники с закругленными углами RoundedRectangle цвет Color. Например, я хочу, чтобы мой прямоугольник был очерчен красным цветом, как в нашей игре Memorize:
Так как мне сделать прямоугольники с закругленными углами RoundedRectangle красным? Хорошо, иду к своему инспектору Inspector, но в моем инспектору Inspector цвета нет.
Когда я работал с текстом Text, там был цвет Color, и я просто выбрал оранжевый цвет Orange и получил этот оранжевый текст Text прямо в предварительном просмотре Preview.
Но этого здесь нет. Так что же мне делать? То, что появляется в инспекторе Inspector, это, обычно, наиболее популярные вещи, которые люди хотят устанавливать. Но все остальное, что вы хотите сделать, доступно через поле поиска в самом низу инспектора Inspector.
Видите? Там есть подсказка: “Add Modifier” («Добавить модификатор»), и это позволит вам добавить любой из модификаторов, которые не появляются в инспекторе Inspector, но вы хотите их использовать.
Если я кликну на поле поиска, то вы увидите, что есть очень много модификаторов, с которыми View умеют обращаться.
Замечательно то, что большинство из них мы рассмотрим к концу этого семестра. Но сейчас это выглядит довольно пугающим длинным списком. Как вам найти среди этого то, что хотите? Ну, вы можете это сделать, просто набрав в поисковой строке, например, Color:
Вам показывают семь вещей, с помощью которых можно изменить цвет Color. Например, Color Invert инвертирует цвет, то есть черный цвет превращает в белый.
Но мы хотим Foreground Color. Если вы его выбираете, то он добавится в инспектор Inspector, как один из разделов. то есть Foreground Color. Нам не нужен синий цвет, мы хотим красный.
И действительно, теперь у нас прямоугольник RoundedRectangle имеет красную границу, которая нам и нужна.
Если мы взглянем на код, то он становится немного беспорядочным, переходящим произвольно на другие строки, вот почему часто приходится видеть, когда каждая отдельная функция находится на своей отдельной строке.
Это очень распространено.
Итак, мы хорошо продвинулись с получением базовых “кирпичиков” Lego (Лего) наподобие прямоугольника с закругленными углами RoundedRectangle или текста Text, модифицируя их так, как нам нужно.
Но если мы в который раз посмотрим на наши карты игры Memorize, то они представляют собой комбинацию (объединение) прямоугольника RoundedRectangle и текста Text. Эти эмоджи (смайлики) представляют собой небольшие текстовые строки.
Итак, как нам совместить эти вещи?
Что ж, хотим вернуть some View, который является View-объединителем (View combiner), как объединитель (combiner) Lego (Лего), о котором мы говорили раньше.
Именно это мы и будем делать и вернем другой View с именем ZStack. Через мгновение я расскажу, что такое ZStack, а сейчас на некоторое время избавимся от нашего прямоугольника RoundedRectangle и попытаемся создать ZStack.
Когда мы добавляем маленькие открывающиеся скобки, то Xcode предлагает нам две разные версии создания ZStack. Одна версия с двумя аргументами: выравниванием alignment и содержимым content, а другая версия с одним аргументом — содержимым content. О выравнивании alignment мы поговорим через секунду, но ключевым аргументом в ZStack является содержимое content. Давайте введем этот аргумент content, это его метка, а какой ТИП имеет этот аргумент? Что ж, оказывается, это функция.
Да, функциональное программирование снова поражает. Эта функция на самом деле возвращает нам пластмассовый “пакетик” Lego (Лего). Вспомните “пакетик” Lego (Лего) в нашей аналогии, когда мы создавали «Тысячелетнего сокола», и именно это должен дать ZStack, он является этим “пакетиком” Lego (Лего).
Swift знает, что вы собираетесь многократно использовать эти “пакетики” Lego (Лего), поэтому он делает создание ZStack таким простым для вас. Желание упростить возвращение View-объединителей (View combiner) в качестве some View, значительно продвинуло саму концепцию функции в Swift.
Функция, которую вы передаете View — объединителю (View combiner), то есть комбайнеру Lego (Лего), называется ViewBuilder. По сути, такой ViewBuilder позволяет вам составить список других Views, а затем сложить их в “пакетик” Lego (Лего) прямо здесь, в коде, и вернуть его.
Так что в ZStack будет находиться “мешок” Lego (Лего) для работы. Все, что нам нужно сделать, это взять те “кирпичики” Lego (Лего), которые мы уже создали, и положить их прямо в ZStack.
Нам не нужны здесь операторы return. Достаточно перечислить “кирпичики” Lego (Лего). Такая функция выглядит синтаксически странно, потому что не задействован return.
Этот механизм, этот ViewBuilder, умеет брать список Views и, фактически, объединить их в другой View, представляющий собой “пакетик” Lego (Лего). ТИП этого другой View вам действительно не нужно знать, но он называется TupleView. Мы никогда не будешь печатать его в нашем коде, потому что для нас это не имеет значения. Это происходит автоматически, этот ViewBuilder механизм сам создает “пакетик” Lego (Лего), а вы будете все время использовать его. Вы все время будете составлять список Views. Но “пакетик” Lego (Лего) намного мощнее, чем просто перечисление Views. Вы также можете использовать здесь if-else. Внутри этих “пакетиков” Lego (Лего) могут быть условные утверждения. Вы даже можете определять там переменные. Но на самом деле не так уж многое что еще можно делать в ViewBuilder.
В основном это списки Views, использование if else, чтобы выбрать, какой View вы хотите иметь в “пакетике” Lego (Лего), ну и ещё некоторые переменные вы тоже можете использовать.
Примечание. Начиная с Xcode 12, оба оператора switch и if let поддерживаюся в ViewBuilder! |
Обратите внимание, что я несколько иначе организовал свой код.
У меня прямоугольник с закругленными углами RoundedRectangle и текст Text располагаются на отдельных строках, но я использовал отступы, поэтому, когда я смотрю в этот “пакетик” Lego (Лего) ZStack, то по левому краю выстраиваются мои фактические Views, а их модификаторы следуют по отношению к ним с небольшим отступом.
Что будет делать ZStack с этим “пакетиком” Lego (Лего), который мы ему передали? ZStack накладывает эти Views друг на друга в направлении от устройства к пользователю. Это именно то, что мы хотим, правда?
Когда мы создаем карту игры Memorize, то у нее есть фон карты поверх фона — приятный эмоджи (смайлик).
В результате появляется стек Views, который направлен к нам.
Есть и другие View-объединители (View combiner), которые складывают вещи по горизонтали или по вертикали, есть View-объединители (View combiner), которые складывают вещи в определенной сетке, как показано на рисунке сверху.
Мы начинаем создание нашего приложения с построения одной карты, но когда у нас уже будет несколько карт, то мы будем использовать некоторые другие View-объединители (View combiner), чтобы комбинировать их по-разному. Вы видите, что на самом деле наш код уже работает: у нас есть фон карты, а затем и наше приветствие “Hello, World!” («Привет, мир!»).
На самом деле мы построили здесь some View, который представляет собой ZStack, содержащий прямоугольник с закругленными углами RoundedRectangle и текст Text. ТИП возвращаемого some View — это не просто ZStack, потому что ТИП ZStack включает в себя ТИП вещей, которые включены в него.
Так что, ZStack на самом деле не годится для ТИПа переменной var body. Вы видите, мы позволили переменной var body быть ZStack, но это не работает. Так вот почему нам нужен some View.
Мы создаем здесь сложную коллекцию Views, объединенных в ZStack, и мы хотим позволить компилятору самому выяснить сложный ТИП, который здесь задействован. Поэтому мы никогда не указываем ТИП вместо some View, мы просто позволяем some View что-то рассчитать для нас.
Я хочу немного поговорить о модификаторах, которые у нас уже есть на Views. В данный момент у нас есть модификаторы, установленные на “кирпичики” Lego (Лего), и они делают их такими, какие нам нужно.
Но давайте поговорим о том, что это значит поставить модификатор на один из этих View-объединителей (View combiner).
Существуют две разных категории модификаторов, которые вы можете поставить на ZStack. Один из них — это Отступы padding(). В настоящее время у нас есть Отступы padding() на прямоугольнике RoundedRectangle и на тексте Text.
Вы видите, что Отступы padding() есть вокруг текста Text. Но, фактически, нам не нужны Отступы padding() вокруг текста Text. Если мы уберем их, то ничего не меняется, все работает, как работало.
У нас также есть Отступы padding() вокруг RoundedRectangle, так как мы не хотим, чтобы прямоугольник располагался прямо у края.
Но мы могли бы поставить Отступы padding() вокруг самого ZStack. Я сниму Отступы padding() у RoundedRectangle и поставлю на ZStack, который я создал.
Это вполне законно, потому что ZStack — это просто View, то есть он “ведет себя” как View, поэтому у него есть функция padding(), и вроде ничего не изменилось, все выглядит так же, но есть очень тонкая разница.
Смотри. Если я кликну на прямоугольнике RoundedRectangle, то вы увидите синюю линию, которая определяет, где находится прямоугольник, и она больше не показывает наличие Отступа padding() вокруг него.
Итак, у прямоугольника RoundedRectangle нет Отступов padding(). Если я кликну на ZStack, то здесь вы увидите Отступы padding(). Синяя линия подходит прямо к краю экрана.
Такие View модификаторы, как Отступы padding(), работают отлично с View-объединителем (View combiner) вроде ZStack. Действительно нет разницы между ZStack, прямоугольником RoundedRectangle и текстом Text в том смысле, что все они — это просто View, и все они, конечно, могут использовать Отступы padding().
Но есть и другая категория модификаторов. Они немного более интересны, если дело доходит до View-объединителей (View combiner). Возьмем, например, модификатор foregroundColor (.red). Уберем его с каждого из наших “кирпичиков” Lego (Лего) и поместим его в ZStack.
Смотри, что произошло. Все внутри ZStack стало красным. Прямоугольник RoundedRectangle и текст Text внутри ZStack стали красными.
Так что в действительности означает установка Цвета с помощью модификатора foregroundColor (.red) для View-объединителя (View combiner) ZStack?
Сами View-объединители (View combiner) на самом деле ничего не рисуют. Они объединяют в себе другие вещи, которые рисуют, а именно прямоугольник RoundedRectangle и текст Text. Модификаторы такой категории передаются ко всему, что находится внутри View-объединителей (View combiner). Размещение foregroundColor (.red) на ZStack означает в точности то же самое, что размещение модификатора foregroundColor (.red) отдельно на прямоугольнике RoundedRectangle и отдельно на тексте Text.
Это как бы установка Цвета внутри ZStack по умолчанию. Если бы я все же хотел, чтобы мой текст Text был оранжевым, то я мог бы использовать модификатор foregroundColor (.orange) для текста Text.
Так что это разрешено. По умолчанию Цвета наследуются от своего контейнера.
Надо помнить, что, в конце концов, у нас будут контейнеры внутри контейнеров, а те — внутри других контейнеров, поскольку мы будем создавать сложные View, и удивительная вещь, эти контейнеры передадут свои Цвета вниз по цепочке до каждого из уровней.
Итак, вся эта сетка карт, расположенных на правом экране, — красная. Вероятно, нам следует установить красный Цвет на самом верхнем уровне. Тогда в этой сетке и все, что внутри этой сетки, в том числе все эти карты, унаследуют красный Цвет, если мы не переопределим его для отдельных частей UI.
Прежде чем мы двинемся дальше, я действительно хочу немного улучшить мой код. Помните, мы говорили о том, что Swift “не любит” иметь ничего постороннего в своем коде? Например, в функции foregroundColor (Color.orange) мы могли бы убрать Color в аргументе, потому что Swift сам может понять, что там происходит. Мы также знаем, что ключевое слово return не нужно.
Выделенная функция возвращает some View. Компилятор может посмотреть код и увидеть, что на самом деле это одна строчка кода. Это похоже на несколько строк кода, но на самом деле это одна строка, возвращающая модифицированный ZStack. Это единственное, что находится в этой функции, и он знает, что нужно вернуть одну вещь, которая соответствует some View. Так что ключевое слово return нам не нужно.
Но есть еще кое-что, что нам также не нужно. Это аргумент ZStack, в нашем случае ZStack принимает только один аргумент. Между прочим, ZStack может иметь еще один аргумент. Это выравнивание alignment. Давайте вставим это аргумент выравнивания alignment. Например, alignment: .top :
Это приводит к тому, что приветствие «Hello, world!» ( «Привет, мир!») и прямоугольник с закругленными углами RoundedRectangle накладываются друг на друга, но они выравниваются по верхней границе вместо значения по умолчанию alignment:.center, когда выравнивание осуществляется по центру.
Избавимся от оранжевой окраски нашего текста Text, мы хотим, чтобы внутри ZStack все было красным.
Второй аргумент — content, его значение — функция, одна из специальных функций ViewBuilder, которые превращают это в “пакетик” Lego (Лего), но это все же функция.
И каждый раз, когда вы создаете структуру struct или даже вызываете функцию func и передаете ей аргумент, значением которого является функция, то если это — последний аргумент, то можно вывести это аргумент — функцию из-под круглых скобок.
Итак, у моего ZStack есть аргумент выравнивания alignment: .center, а теперь и эта функция за пределами вызова ZStack (alignment: .center). Это типичная ситуация для этой функции — “тусоваться” за пределами вызова ZStack, прямо в коде переменной var body.
Мы можем просто оставить все как есть. Зачем мы это делаем? Почему Swift позволяет нам иметь последний аргумент в таком виде? В настоящий момент даже можно иметь последние пару аргументов в таком виде, если эта пара аргументов — обе функции. Почему мы с этими последними аргументами “тусуемся” за пределами списка параметров?
Исключительно для внешнего вида. Потому, что это выглядит хорошо, и даже было бы ещё лучше, если бы у нас не было другого аргумента.
В этом случае вообще можно избавиться от круглых скобок.
Теперь у вас есть этот красивый маленький код, и он говорит: «О, у меня есть ZStack с этими двумя вещами.” Мы всегда будем так делать. Никто никогда не печатает content:, никаких ТИПов, все убираем.
Если мы хотим использовать выравнивание alignment, это нормально, мы можем вернуться и добавить alignment:.center, но если мы этого не делаем, то всегда используем этот очень простой код.
Многие люди сразу погружаются в SwiftUI, и они начинают прямо с этого кода, не понимая на самом деле даже, что здесь происходит, но это всего лишь функция, которая является аргументом ZStack, точнее content аргументом ZStack. И это не просто функция, а особая функция, ViewBuilder, в котором вы просто можете составить список Views, а ViewBuilder упаковывает их для вас. Не более того.
Мы будем придерживаться нашего расписания лекций во время пандемии, которое предполагает проведение двух 80-минутных лекций в неделю.
Так что я собираюсь остановиться здесь, и мы продолжим следующий раз этот демонстрационный пример. Создадим нескольких карт, сделаем их более привлекательными, а затем заставим их немного прокручиваться, добавим еще кнопок и т. д.
Для получения дополнительной информации посетите https://cs193p.sites.stanford.edu/.