Лекция 13. Представляющие (Presenting) Views. Навигация. CS193P Spring 2023.

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

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

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

TextField и @Binding в действии

Следующее, самое важное — я хочу иметь возможность редактировать название палитры, а также добавлять эмодзи (смайлики).
Как мне сделать это редактируемым текстовым полем?
Редактируемые текстовые поля в Swift — это View отличные от обычного текста Text. Вместо Text они называются TextField.

TextField имеет два аргумента.
Первый аргумент — это то, что мы называем Placeholder (Заполнитель) текста или слово, которое можно использовать, чтобы помочь пользователю понять, о чем мы здесь просим.
Второй аргумент, он называется text — это тот текст, который мы редактируем внутри этого текстового поля TextField.
И еще этот второй аргумент является привязкой Binding.

Если подумать о том, что здесь происходит, то у нас есть текстовое поле TextField, в котором текстом text является “Vehicles”. Мы хотим иметь возможность передать текстовому полю “Vehicles” как начальное значение, и всякий раз, когда что-то меняется в нем, мы хотим знать об этом.
И то, как мы собираемся это сделать, заключается в создании единственного “источника истины” (single source of truth) для этого текста text. Мы будем делать это с помощью привязки Binding.
В частности, этот второй аргумент text текстового поля TextField является привязкой Binding.
TextField знает, что он не хочет поддерживать копию того, что редактируется, он хочет редактировать эту вещь напрямую. Поэтому он просит вас дать ему привязка Binding к этому “источнику истины” (source of truth).
Ну a что является “источником истины” (source of truth) для этого palette.name?
Он находится в нашей ViewModel, в нашем PaletteStore. Следовательно, нам нужно дать здесь нашему TextField обратную привязку Binding к нашей ViewModel.
Для этого нам нужна привязка Binding к палитре palette, которую нам дали отредактировать в верхней части нашего PaletteEditor:

Сделав эту переменную var @Binding, мы заставляем того, кто создаст этот PaletteEditor, дать нам привязку Binding к “источнику истины” (source of truth) для этой палитры palette.
Теперь каждый раз, когда мы ссылаемся на эту палитру palette где угодно в нашем коде здесь, в редакторе PaletteEditor, на самом деле мы будем ссылаться в обратном порядке на палитру в нашей ViewModel.
И мы также можем использовать эту привязку Binding для передачи привязки Binding к имени палитры palette.name в нашем TextField:

Это потому, что для $, то есть projectedValue, для @Binding — это еще одна привязка Binding к той привязке @Binding.
Итак, $palette.name здесь означает привязку Binding к этой @Binding var palette, которая будет привязана в обратный порядке в конечном итоге к нашей ViewModel. Теперь наше текстовое поле TextField будет редактировать имя name палитры palette напрямую в ViewModel.
Вы видите, что эти $, эти привязки Binding, проходят через всю нашу систему Views.

Читать далее

Лекция 12. Постоянное хранение (Persistence). Обертки свойства (Property Wrappers). CS193P Spring 2023.

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

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

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

UserDefaults для сохранения палитр palettes 

Давайте займемся нашими палитрами. 
Вы видите палитры прямо здесь?
Если я зайду в свои палитры и скажу New (новая), то добавляется математическая палитра  Math. Если я заново перезапущу приложения, то мы обнаружим, что  математическая палитра  Math исчезла. 

Приложение не помнит, что я добавил палитру  Math (Математика). 
Или, если я удалю что-то, например, Sports, ну, нам не нравятся виды спорта. 
Если мы вернемся и перезапустим наше приложение, то палитра Sports возвращается. 

Итак, мы хотим сделать так, чтобы все, что мы здесь делаем с палитрами, новые и удаленные палитры запоминались.
Мы запомним это в UserDefaults, главным образом потому, что я уже показал вам, как это сделать в файловой системе, теперь я хочу показать, как это делать в UserDefaults. Вероятно, как мы говорили ранее, это не совсем уместно делать это в UserDefaults. Но учитывая довольно маленькое количество данных, мы собираемся сделать это в UserDefaults.
Мы сделаем это очень крутым способом.
Видите мои @Published палитры palettes, которые находятся в моем PaletteStore?

Позвольте мне избавиться от этого кода и превратить palettes в вычисляемое свойство:

Читать далее

Лекция 11. Жесты, вторая MVVM. CS193P Spring 2023.

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

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

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

Демо: масштабирование и перемещение по экрану EmojiArt

Мы хотим иметь возможность масштабировать и перемещать по экрану наш документ EmojiArtDocument с помощью движения пальцев.
Вот как это выглядит, это немного устаревшая версия приложения Emoji Art, но я собираюсь выполнить жест pinch. Кстати, когда вы используете такой симулятор, вы можете выполнить жест pinch, удерживая клавишу option. И видите, у меня сразу на симуляторе появляются тут два пальца в виде серых кружков. Удерживаю option — и они появляются. Можно также перемещать с помощью жеста drag документ по экрану.
Итак, я увеличиваю масштаб. Видите? Я выполняю жест pinch, чтобы увеличить масштаб. И, конечно, я могу уменьшить масштаб. Возможно, я захочу переместить мой документ и я выполняю жест drag.


Мы хотим перемещать документ с помощью жеста drag и изменять его масштаб с помощью жеста pinch, я собираюсь реализовать эти две вещи.
Что мы должны перетаскивать drag и масштабировать pinch.?
Содержимое нашего документа, это фоновое изображение и все эти эмодзи (смайлики). Все это увеличивается и перемещается, вы видите это представлено синим цветом:

Я собираюсь взять этот код и разместить его в отдельной переменной var с именем documentContents, и это то, что я собираюсь масштабировать и перемещать по экрану.

Читать далее

Лекция 10. EmojiArt. CS193P Spring 2023.

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

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

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

Демо. “Сброс” Drop фонового изображения background

Давай сделаем “сброс”  (drop) URL-адреса фонового изображения background. Это действительно очень очень легко. 
Куда мы хотим это “сбросить”?
По сути, мы хотим “сбросить” его поверх нашего ZStack, это наш документ.
Итак, модификатор .dropDestination.
Что мы сбрасываем?
URL-адрес:

Обратите внимание на этот URL.self, когда вы пишите .self на нижнем регистре, то это означает сам ТИП. Таким образом, я передаю .dropDestination ТИП URL.self в качестве аргумента, так что мой документ знает, что мы ожидаем при “сбросе”. В замыкании у нас есть массив URL-адресов urls и место “сброса” location.
Для фонового изображения background нам неважно место “сброса” location, но это нас явно интересует при “сбросе” эмодзи (смайликов), и мы должны поработать с этим location.
Это замыкание должно вернуть информацию о том, был ли “сброс” успешным, потому что некоторые “сбросы” на этот View могут вас не устраивать. Тогда вам придется сказать:”НЕТ, лети обратно, туда, откуда пришел”.
Я собираюсь вернуть return функцию func drop (urls, at: location, in: geometry), которую я собираюсь написать через секунду:

Читать далее

Лекция 9. Анимация (часть 2). CS193P Spring 2023.

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

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

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

Колоду карт довольно просто реализовать.

Это может быть private переменной var, которая является some View и представляет собой ZStack несданных карт undealtСards. Не сданные карты undealtСards находятся прямо вверху и я создал их ранее. Мы будем рисовать несданных карт undealtСards с помощью того же CardView, что мы нарисовали любые другие карты:

Но есть кое-что, что я хочу сделать с этим — я хочу явно указать размер колоды карт с помощью маленького ViewModifier, который я покажу сегодня — это .frame, им очень легко злоупотребить, будьте очень осторожны при его использовании.
О чем .frame?
Я говорил вам, что Views сами решают, какого размера они хотят быть, помните это?
Они могут это сделать с помощью .frame. Но сам по себе .frame в реальности не будет определять размер самого View. Это больше похоже на создание контейнера этого размера и предложения View этого пространства.

Помните, как при размещении Views на экране (Layout) вам предлагается пространство, a View сам определяет свой размер, чтобы там поместиться?
Таким образом, .frame предоставит вам пространство.
Теперь CardView использует всё предоставленное ему пространство.
Итак, я могу написать .frame(width: deckWidth, height: deckWidth / aspectRatio), где deckWidth — это ширина моей колоды карт, и я позже сделаю для нее константу. Высота колоды, конечно же, будет равна ширине колоды деленной на соотношение сторон aspectRatio:

Написав этот код, мы получили небольшой контейнер, в котором нарисовались все наши карты. Это именно то, что я хочу.
Но есть и другие вещи, которые вы можете делать с помощью .frame, a не только выбирать конкретные ширину и высоту. Я хочу, чтобы вы посмотрели в документации его возможности. Например, вы можете указать желаемую минимальную ширину, или желаемые максимальную ширину и высоту.

Теперь причина, по которой я сказал, что этим легко злоупотребить. 
Посмотрите, как мы программируем, мы никогда не указываем точные размеры вещей, мы просто размещаем их в VStacks и HStacks или располагаем в AspectVGrid и все такое. И мы даже использовали GeometryReader, чтобы узнать, a сколько у меня места? Потом мы разделяли полученное пространство между нужными нам Views, которые как бы отреагировали на то, сколько места им дали..
Единственный раз, когда мы выбрали фиксированный размер — это наш теперешний случай, когда мы создали пространство фиксированного размера, чтобы наш View (колода карт) в нем разместился.
И мы даже использовали GeometryReader, чтобы узнать, a сколько у меня места? Потом мы разделяли полученное пространство между нужными нам Views, которые как бы отреагировали на то, сколько места им дали.
Здесь я выбираю конкретную ширину колоды deckWidth равную 50:

Читать далее

Лекция 8. Анимация (часть 1). CS193P Spring 2023.

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

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

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

Итак, я говорил вам в начале семестра, что я постараюсь научить вас всему на этом курсе как минимум три раза. Один раз я расскажу вам об этом на слайдах, потом я покажу это вам на демонстрационном примере, а потом вы сами сделаете это в своем Домашнем Задании.
И это в большой степени верно для анимации.
Итак, вот та часть, где я рассказываю вам про анимацию, то есть какие бывают анимации, как это работает и так далее.
Начнем с фундаментальных основ анимации: что нужно знать, чтобы действительно понять, что такое анимация.
Главное, что нужно понять, это то, что анимация просто показывает изменения в вашей модели  Model с течением времени. Вот и все.

Анимация показывает вам ИЗМЕНЕНИЯ с течением времени, и эти изменения отражаются через аргументы модификаторов ViewModifier и, очевидно, что геометрические фигуры Shape могут меняться.

И еще, я выделил это в отдельную вещь “Переходы” (transitions), но вы увидите, что на самом деле это всего лишь первый вариант, то есть когда Views приходят на экран и уходят с экрана, то работает пара модификаторов ViewModifier.
Когда View появляется на экране, вы хотите, чтобы он “проявлялся” постепенно.
Когда View уходит за пределы экрана, вы хотите, чтобы он «улетал» или постепенно «растворялся» или что-то вроде этого.
Итак, это три вида изменений, которые происходят, и которые мы пытаемся анимировать.
И теперь, пожалуй, самая важная строка на этом слайде.

Анимация показывает вам изменения, которые уже произошли, и анимация просто показывает их вам растянутыми во времени.

Людям, которые не занимались асинхронным программированием или чем-то подобным, нужно привыкнуть к этому. Вам хочется думать, если у вас есть анимация, длящаяся в течение 5 секунд, например, анимация непрозрачности opacity или что-то в этом роде, что переменная var, которая устанавливает непрозрачность opacity как-то меняется со временем, но это не так.
Когда вы устанавливаете непрозрачность opacity для View на 1 от 0, то View постепенно «проявляется» (fade in) в течение 5 секунд, но непрозрачность opacity для вашего View мгновенно изменилась на 1 для всего вашего View.
Просто система анимация показала пользователю это изменение в течение 5 секунд.

Итак, все изменения, которые стоят за анимациями, которые вы видите, произошли мгновенно, a вы их видите растянутыми во времени.
Как только вы поймете это и отложите в своей голове, то сможете вложить гораздо больше смысла в написание кода анимации, который будет правильно работать.

Таким образом, модификаторы ViewModifiers — это основные агенты изменений в пользовательском интерфейсе (UI). Большинство вещей, которые меняются, например непрозрачность opacity, соотношение сторон aspectRatio, даже Views, летающие вокруг, это просто модификатор ViewModifier position.
Есть такой модификатор ViewModifier, который я вам еще не показывал, он называется position, и такие контейнеры Views, как HStack и LazyVGrid, используют этот модификатор position для размещения своих Views. Когда Views “летают” вокруг, это просто потому, что аргумент в их модификаторе позиция position меняется.
Итак, модификаторы ViewModifiers — это действительно то, что заставляет всё двигаться и анимировать, и вы увидите это в демонстрационном примере, который мы сделаем.

Читать далее

Лекция 7. Shape, ViewModifier, Constants. CS193P Spring 2023.

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

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

Что мы делаем сегодня? 

. . . . . . . . . .

Одна из анимаций, которые мы собираемся сделать в ближайшее время, это небольшой таймер обратного отсчета.
И это действительно воодушевляет вас быстрее выбирал карты, потому что вы получаете больше очков. Чем больше отсчитывается времени до выбора вами карты, тем меньше очков вы получите к моменту совпадения карт.
Итак, давайте посмотрим, как это происходит. Обратите внимание на таймер обратного отсчета. Если я позволю этому таймеру идти до конца, то я не получу много баллов, даже если произойдет совпадение карт.
Если я переверну карту обратно, то таймер останавливается.

Вы видели, как взлетела маленькая цифра +2? Это говорит о том, сколько я получил баллов.
Позвольте мне еще раз попытаться получить больше очков. Теперь хорошо, количество очков +13.

Демонстрационный пример Shape

Итак, мы собираемся создать этот маленький круглый “пирог” Pie, который ведет обратный отсчет. Мы не будем анимировать его сегодня, но мы создадим свою собственную геометрическую фигуру Shape в виде маленького “пирога”.

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

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

Давайте начнем с нашего CardView, который вы видите здесь на экране.
Я размещу там круг Circle ( ) вместо “пирога” Pie:

И это ZStack. Они сгруппированы с помощью Group, но это все еще ZStack
И я просто помещаю Circle() позади текста Text. Мы почти закончили, правда?
Это не “пирог” Pie, а круг Circle(), и он имеет ярко-оранжевый цвет, пожалуй, слишком яркий, ведь таймер не так уж и важен. Давайте к нашему кругу Circle() добавим немного непрозрачности
.opacity ( 0.5 ):

Можно было бы снизить до .opacity ( 0.4 ):

Видите, круг стал немного более прозрачным.
Еще одна вещь, которая мне не нравится, это то, что мой “пирог” Pie подходит прямо к краю карты. Это не так уж хорошо.

Читать далее

Лекция 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 для исполнения этого «танца». Я покажу, как выглядит этот протокол, когда мы доберемся до демонстрационного примера.

Читать далее

Лекция 5. Протоколы, перечисления enum, Optional. CS193P Spring 2023.

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

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

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

Анимация карт cards

Я сказал вам, что не собираюсь показывать вам анимацию на этой неделе. Я не собираюсь показывать вам это и на следующей неделе, даже после того, как мы потратим всю неделю на изучение анимации. Об анимации нам предстоит многое узнать. Анимация очень важна в мобильных приложениях на iPad или iPhone, и очень важно иметь хорошую анимацию.
Но сегодня
я собираюсь показать вам самую примитивную анимацию, потому что она поведет нас по пути реализации протоколов protocol, соответствия протоколам, превращения наших “не важно каких” (don’t care) Generic ТИПов в “немного важно какие” Generic ТИПы с ограничениями и все такое.

Я добавлю немного анимации к нашим картам cards.
Вот наши карты cards на UI. Вы видите, что здесь есть ScrollView, VStack — это наши карты cards, которые представляют собой сетку LazyVGrid:

Я хочу применить анимацию к моим картам cards, и собираюсь использовать значение по умолчанию .default для вида анимации. Это только один из возможных видов анимации, которую мы можем задать с помощью этого View модификатора. Мы поговорим обо всех других видах анимации позже.

Видите этот ViewModifier? Он меняет этот View, чтобы наделить его анимацией.
Здесь есть аргумент value. Какое значение мы должны ему дать? Все что угодно, любую переменную var, которую вы хотите, и анимация будет происходить только, если внутри этого View значение value изменится. Мы действительно должны указать то, что может вызвать анимацию наших карт cards, и это viewModel.cards:

По сути, когда любая из наших карт изменится, нам нужно её анимировать.
Вот почему я размещаю здесь viewModel.cards в качестве value. Итак, если наш viewModel.cards изменится, то любое из этих изменений вызовет анимацию. Например, когда я кликну на кнопке Shuffle (Перетасовать),  то произойдет анимация перетасовки карт.

Протокол Equatable

Читать далее

Лекция 4. Применение MVVM. CS193P Spring 2023.

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

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

Итак, вся эта неделя будет посвящена этой картинке, которую я показал вам на прошлой Лекции.

Она относится к нашей игре Memorize таким образом, что мы собираемся использовать её для построения ЛОГИКИ игры, а к концу Среды мы уже на самом деле будет играть в игру Memorize.
Давайте просто вернемся к нашему коду, к тому места, на котором мы остановились в прошлый раз.

Позвольте мне дать некоторый обзор того, где мы находимся на данный момент, по сравнению с представленной выше картинкой. То, что вы видите здесь, этот класс class : EmojiMemoryGame, наша ViewModel. На первой и нашей основной картинке это зеленая вещь:


А это наша Model (Модель). На первой картинке это синяя штука внизу.

В настоящее время мы уже создали наше View, оно называется ContentView:

Мы собираемся изменить его на что-то другое, но кусочек его мы уже создали. Обратите также внимание, что когда я кликнул на файле MemorizeGame, то увидел, что там находится структура struct с именем MemoryGame. Давайте просто переименуем файл, вам разрешено выделять имена файлов и …

… изменять их:

Если вы хотите переименовать актуальную структуру struct, тогда вам нужно переименовывать немного больше вещей, поскольку вы даете новое имя структуре struct, файлу и другим вещам, ссылающимся на эту структуру struct. На самом деле я собираюсь показать вам, как это сделать, когда мы будем переименовывать структуру struct ContentView в EmojiMemoryGameView, это имя значительно лучше слишком обобщенного имени ContentView. Я покажу вам это через секунду. Если вы вернетесь в ваш ViewModel, вы заметите, что там мы создали переменную var для нашей модели model:

На первой и основной картинке наш ViewModel имеет полную возможность подключения к Model (Модели). Он может «поговорить» с Model (Моделью) обо всём, что ему нужно, потому что вся его работа именно в этом и состоит: чтобы понять Model (Модель), «поговорить» с ней, интерпретировать её данные и представить их View действительно наилучшим способом. 

Иногда мне нравится называть ViewModel “дворецким” (butler) Views. Вы когда-нибудь смотрели «Аббатство Даунтон» или что-то подобное? Там вы видите этих старых “дворецких”, они накрывают на стол, чтобы вы вкусно пообедали, и они устраивают все ваши дела. Именно это и делает ViewModel, беря все, что есть в Model (Модели), и организуя это наилучшим образом, чтобы у View был такой красивый и простой код, что всё действительно понятно.
В этом и состоит роль ViewModel
Конечно, у ViewModel  есть переменная var model прямо в коде:

Читать далее