Русский неавторизованный конспект лекций Стэнфордского университета " Разработка iOS приложений" 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 и 2023 гг., сопровождаемый решениями заданий и дополнениями, связанными с адаптацией курсов к новым версиям Swift, Objective-C и iOS.
Ниже представлен небольшой фрагмент Лекции 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 Стэнфордского курса 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 функцию funcdrop (urls, at: location, in: geometry), которую я собираюсь написать через секунду:
Ниже представлен небольшой фрагмент Лекции 9 Стэнфордского курса CS193P Весна 2023«Разработка iOS приложений с помощью SwiftUI«. Полный русскоязычный неавторизованный конспект Лекции 9 в формате Google Doc и в виде PDF-файла, который можно скачать и использовать offline, доступны здесь. Код находится на GitHub.
С полным перечнем Лекций и Домашних Заданий на русском языке можно познакомиться здесь.
. . . . . . . . . . . . . .
Колоду карт довольно просто реализовать.
Это может быть private переменной var, которая является someView и представляет собой 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 Стэнфордского курса 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 Стэнфордского курса 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 Стэнфордского курса CS193P Весна 2023«Разработка iOS приложений с помощью SwiftUI«. Полный русскоязычный неавторизованный конспект Лекции 6 в формате Google Doc и в виде PDF-файла, который можно скачать и использовать offline, доступны здесь. Код находится на GitHub.
С полным перечнем Лекций и Домашних Заданий на русском языке можно познакомиться здесь.
На сегодняшней Лекции 6, a, по сути, на всей этой неделе мы подберем несколько тем и попытаемся понять больше о том, как то, что мы делали, действительно работает.
Первая тема — это Layout, то есть расположение Views на экране. Как место на экране распределяется между всеми Views с помощью HStack, LazyVGrid, ScrollView и всех других подобных вещей? Как они решают, кому какое место достанется? Затем у нас будет небольшая демонстрация.
Аналогичная ситуация с @ViewBuilder. У нас есть есть такая классная штука как @ViewBuilder. Мы знаем, что это просто функция, которая возвращает someView. Но мы также знаем, что это круто и что это список 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. И это всегда работает таким образом, и у этого есть действительно хороший маленький протокол protocolLayout для исполнения этого «танца». Я покажу, как выглядит этот протокол, когда мы доберемся до демонстрационного примера.
Ниже представлен фрагмент Лекции 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 (Перетасовать), то произойдет анимация перетасовки карт.
Ниже представлен фрагмент Лекции 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 есть переменная varmodel прямо в коде:
Ниже представлен начальный фрагмент Лекции 3 Стэнфордского курса CS193P Весна 2023«Разработка iOS приложений с помощью SwiftUI«. Полный русскоязычный неавторизованный конспект Лекции 3 в формате Google Doc и в виде PDF-файла, который можно скачать и использовать offline, доступны здесь. Код находится на GitHub.
С полным перечнем Лекций и Домашних Заданий на русском языке можно познакомиться здесь.
В этой Лекции вы увидите, наверное, самое большое количество слайдов на протяжении всего семестра, но нам нужно изучить базовую архитектуру прежде, чем погрузиться в следующую часть демонстрационного приложения, которая будет освещаться во второй половине сегодняшней Лекции. Есть вопросы, прежде чем я начну? Хорошо, нет. Итак, Лекция номер 3. У нас есть две очень большие темы для разговора: одна из них — MVVM, которая представляет собой архитектуру, парадигму дизайна, которую вы будете использовать для создания iOS приложений, вторая — система ТИПов Swift:
Сегодня
MVVM
Парадигма конструирования
Система ТИПов Swift
struct — структура
class — класс
protocol — протокол
“Don’t Care” ТИП (Generic) — “Не важно какой” ТИП (Дженерик)
enum — перечисления
functions — функции
Возвращаемся к Демо!
Применение MVVM к Memorize
В Swift есть разные ТИПы: структура struct, протокол protocol и многое другое. Итак, это две основные темы Лекции 3, а затем я вернусь к демонстрационному приложению, в которой мы начнем создавать логику игру Memorize, то есть что происходит, когда вы кликаете на карты.
Архитектура MVVM
Итак, что это за штука MVVM? Надо сказать, что в Swift очень важно отделить логику и данные вашего приложения от пользовательского интерфейса (UI). Это действительно очень важно.В некотором смысле, SwiftUI построен на той идее, что у вас будут ДАHHЫЕ и ЛОГИКА, которые связаны с тем, что делает ваше приложение, и еще у вас будет UI, который покажет это пользователю и будет взаимодействовать с пользователем как совершенно отдельная вещь. Так что это реально очень важно. Та часть приложения, которая связана с ДАННЫМИ и ЛОГИКОЙ, например, в нашем приложении Memorize это “что происходит, когда вы кликаете на карте?” или “карты лежат лицом вверх или лицом вниз?”, все это, мы называем Model (Моделью) нашего приложения. Вы услышите, что я использую это слово Model (Модель) сотни раз. Вся логика и данные “живут” в Model (Модели).
Всё, что мы делали до сих пор на этом курсе, был только UI и иногда мы будем называть его View, потому что это наш «взгляд», наш портал на Model (Модель).
Model и UI
Отделение «Логика и данных» от UI
SwiftUI очень серьезно относится к отделению логики и данных приложения от UI
Мы называем логику и данные нашей Model (Моделью)
Это может быть структура struct или SQL база данных или некоторый код машинного обучения или многие другие вещи
Или любая комбинация таких вещей
UI — это “параметризованная” оболочка, которую “питает” и вызывает к “жизни” Model
Думайте о UI как о “визуальном” проявленииModel (Модели)
Model (Модель) — это где “живут” такие вещи как isFaceUp и cardCount (a не @State в UI)
SwiftUI заботится о том, чтобы UI перестраивался всякий раз, когда Model (Модель) изменяется
Model (Модель) может быть одной структурой struct. Это может быть целая SQL база данных, полная всяких сущностей. Возможно, что это может быть какой-то код машинного обучения. Это может быть похоже на REST API для Интернета. Это может быть почти что угодно. Наша Model (Модель) — концептуальная вещь, это не просто одна структура struct, хотя в нашем приложении Memorize это будет действительно одна структура struct, так как у нас очень простое маленькое стартовое приложение. Но я не хочу, чтобы вы думали, что ваша Model (Модель) не может быть более мощной и сложной.
Что касается UI части нашего приложения, то это на самом деле просто “параметризованная” оболочка, которую Model “кормит”. Одна из лучших фраз, которые я слышал о UI — это визуальное проявлениеModel, потому что Model — это то, чем реально является ваше приложение, это игра на запоминание Memorize, вот что это такое, так что вся эта логика игры находится в Model. UI — это просто то, как вы показываете Model пользователю, то есть её визуальное проявление. Вам действительно хочется думать об этом именно так. То, что мы разместили наши переменные isFaceUp и cardCount в @State, принадлежат Model. Всё это касается игры, в которую мы играем. Поэтому мы не хотим, чтобы они были в UI, и я постараюсь избавиться от них в UI, поместив их в нашу Model.
Одна из очень важных вещей, которые делает Swift, супер важная его обязанность, заключается в том, чтобы обеспечить отражение в UI любых изменений в Model. Model отображается в UI. И для этого у Swift есть огромная инфраструктура для этого. В свою очередь ВАМ НЕ нужно нести ответственность за то, что все, что есть в вашей Model, отображалось бы в UI. Swift всё это сделает за вас. Вам же просто нужно дать Swift несколько подсказок о том, что в Model влияет на ваш UI. Как только вы сделаете это единожды, дальше волноваться об этом не придется. Беспокоиться следует о том, чтобы разделить эти вещи: Model и UI. Но если мы разделим Model и UI, как они будут между собой взаимодействовать? Потому что очевидно, что им нужно поговорить друг с другом.
Я свел это к трем различным способам коммуникации Model и UI. Возможно, существуют и другие способы, но большинство вещей уместилось бы в одной из этих трех категорий.
Model и UI
Подключение Model к UI
Существует несколько вариантов подключения Model к UI …
Редко, но Model может быть просто @State в View (это минимум разделения, его практически нет)
Model может быть доступна только через “привратника” (gatekeeper) — класс class “View Model” (полное разделение)
Существует “View Model” класс class, но Model все еще доступна View напрямую (частичное разделение)
Почти всегда этот выбор зависит от сложности Model. . .
Model представляет собой SQL + struct(s) + что-то еще — скорее всего опция #2
Model представляет собой простейший кусок данных и не имеющая практически никакой логики — скорее всего опция #1
Что-то между опцией 1 и опцией 2 — опция #3
Сегодня мы будем говорить об опции #2 (полное отделение).
Мы называем такую архитектуру, которая подключает Model к UI таким образом, MVVM.
Model — View — ViewModel
Это основная архитектура для любого разумного по сложности SwiftUI приложения
Мы быстро взглянем на то, как использовать #3 (частичное разделение) путем минимальных изменений MVVM.
Редко, но один из способов, каким можно подключить Model к UI, заключается в том, что Model (Моделью) является @State в View. Я имею в виду, что если вы всю Model, всю логику в вашем приложении разместите в @State в вашем View, то, очевидно, View может получить к ней доступ. Это крайне минимальное разделение. Я бы, наверное, не стал этого делать.
Давайте поговорим о том, что мы будем делать в ближайшее время. Номер 2 заключается в том, что Model доступна для UI только через “привратника” (Gatekeeper). У нас будет привратник (Gatekeeper), classViewModel, и работа этого “парня” — поддерживать безопасную коммуникацию UI и Model. Это основной способ, который мы будем использовать в наших приложениях, и этот способ называется MVVM. Номер 2 мы будем использовать в 99% случаев при создании приложений
Номер 3 является своего рода гибридом первых двух, то есть у нас есть этот привратник (gatekeeper), который мы называем ViewModel, но иногда у нас есть прямой доступ к Model. У нас будет public переменная var в Model, которая доступна UI и через неё UI на самом деле разговаривает с Model напрямую, прямо с игрой Memorize. По сути, это гибрид первых двух.
Итак, как узнать, какой из этих вариантов использовать?
Ниже представлен начальный фрагмент Лекции 2 Стэнфордского курса CS193P Весна 2023«Разработка iOS приложений с помощью SwiftUI«. Целиком конспект Лекции 2 можно прочитать на Google Doc здесь. Код находится на GitHub.
С полным перечнем Лекций и Домашних Заданий на русском языке можно познакомиться здесь.
Иногда мне нравится начинать свои Лекции с возврата на предыдущую лекцию и доделывать всё, что забыл там сделать. Вы видели, как проходят Лекции — мне нужно слишком многое вам рассказать. и иногда я слишком быстро что-то проскакиваю.
Режим Выбора (Selection) в Preview
Итак, я хотел бы начать с возвращения к предыдущей Лекции и немного уточнить одну вещь, связанную с инспектором.
Помните, я говорил вам, что вы можете войти в режим Выбора (Selection) внизу вашего PreView. В режиме Выбора (Selection) вы сможете выбирать элементы вашего UI или даже несколько элементов одновременно.
Пару замечаний об этом. Я говорил о том, почему это важно, ведь если вы умеете программировать, зачем вам это нужно? И я приводил примеры, кому это нужно: локализаторам или дизайнерам вашего UI, которые могут и не быть программистами. Однако не значит, что это не имеет абсолютно никакой важности лично для вас. Это может быть хорошо для настройки определенных элементов UI.
И еще одна вещь, которую следует отметить по этому поводу. Я не думаю, что я упомянул о том, что этот режим Выбора (Selection) работает в обе стороны. Если я кликну на строка кода, то будет выбраны (голубой рамкой) элементы UI, представляющие этот код:
И наоборот, если вы выберите элемент UI, то будет подсвечен код, представляющий этот элемент UI. Другими словами, дело не только в том, что ВЫ выбираете, а что выбирает XCode. Если вы выберете любой из двух вариантов, то будет выбран другой, поэтому я хочу, чтобы вы это поняли. И тогда как программисту, почему бы вам не повозиться с цвет или с некоторыми отступами или с чем-то еще в этом роде без необходимости постоянно печатать и редактировать исходный код. Это зависит от того, что вам нравится. Лично я люблю редактировать исходный код. Это как бы вытекает из моего мироощущения, поэтому мне нравится все делать в коде. Но некоторым людям нравится кликать на кнопки. И поэтому я просто не хотел принижать возможности Инспектора и режима Выбора (Selection). Это может быть интересным и значимым. Итак, это было единственное, к чему из прошлой Лекции я хотел вернуться .
Настройки проекта Project Settings
Еще одна вещь, которую я хотел бы упомянуть. Когда мы вышли на Навигатор в раздел Файлы, я сказал: “Посмотрите, есть три файла: MemorizeApp, ContentView и Assets. Но на самом деле есть еще одна вещь, вы можете кликнуть на самой верхней строке иерархии, это что-то вроде файла, и увидеть настройки Project Settings вашего проекта.