Лекция 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 прямо в коде:

Читать далее

Лекция 3. MVVM. CS193P Spring 2023.

Ниже представлен начальный фрагмент Лекции 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 …

  1. Редко, но Model может быть просто @State в View (это минимум разделения, его практически нет)
  2. Model может быть доступна только через “привратника” (gatekeeper) — класс classView Model” (полное разделение)
  3. Существует “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.
  1. Редко, но один из способов, каким можно подключить Model к UI, заключается в том, что Model (Моделью) является @State в View. Я имею в виду, что если вы всю Model, всю логику в вашем приложении разместите в @State в вашем View, то, очевидно, View может получить к ней доступ. Это крайне минимальное разделение. Я бы, наверное, не стал этого делать.
  2. Давайте поговорим о том, что мы будем делать в ближайшее время. Номер 2 заключается в том, что Model доступна для UI только через “привратника” (Gatekeeper). У нас будет привратник (Gatekeeper), class ViewModel, и работа этого “парня” — поддерживать безопасную коммуникацию UI и Model. Это основной способ, который мы будем использовать в наших приложениях, и этот способ называется MVVM. Номер 2 мы будем использовать в 99% случаев при создании приложений
  3. Номер 3 является своего рода гибридом первых двух, то есть у нас есть этот привратник (gatekeeper), который мы называем ViewModel, но иногда у нас есть прямой доступ к Model. У нас будет public переменная var в Model, которая доступна UI и через неё UI на самом деле разговаривает с Model напрямую, прямо с игрой Memorize. По сути, это гибрид первых двух.

Итак, как узнать, какой из этих вариантов использовать? 

Читать далее

Лекция 2. Ещё больше SwiftUI. CS193P Spring 2023.

Ниже представлен начальный фрагмент Лекции 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 вашего проекта. 

Читать далее

Как превратить единственный контейнер в dropDestination для нескольких Transferable типов ?

Новый протокол Transferable был представлен на WWDC 2022 и призван значительно сократить усилия, необходимые с нашей стороны для копирования и вставки (Copy & Paste), a также для перетаскивание и “сброса” (Drag & Drop) данных внутри одного приложения или между разными приложениями.

 Он пришел на замену классу NSItemProvider в iOS 16+, macOS 13+ (Ventura и новее), watchOS 9.0+ и tvOS 16+. Познакомиться с некоторыми аспектами применения протокола  Transferable можно в постах Протокол Transferable меняет правила игры для Drag & Drop в SwiftUI и Протокол Transferable в SwiftUI — передача альтернативного контента с помощью ProxyRepresentation.

Однако попытка использовать протокол  Transferable в демонстрационном примере EmojiArt из  курса CS193P Стэнфордского университета, Весна 2021 (Лекция 9. Drag &Drop) и заменить класс class NSItemProvider на протокол Transferable привела к вопросу о том, как поддерживать “сброс” (Drop) нескольких Transferable ТИПов в один контейнер.

EmojiArt — это приложение, которое позволяет пользователю создавать определенную «картину», зайдя в интернет и выбрав там понравившееся изображение, которое можно «бросить» (Drop) на свой EmojiArtDocument в качестве красивого фонового изображения, а затем добавив к нему эмодзи (смайлики), создать красивое произведение искусства. Нам нравятся эмодзи. Эмодзи можно выбрать из любой тематической палитры, «бросать» их на создаваемую вами «картину» и передвигать их повсюду, изменять их размер. В нижней части экрана у нас целый спектр палитр эмодзи на разные темы.

В таком приложении мы должны предоставить пользователю возможность перетаскивать и «бросать» (Drag & Drop) строку String, URL-адрес или данные Data (например, данные изображения) в один и тот же ZStack. И до появления протокола  Transferable это было сделано с помощью класса class NSItemProvider и View модификатора .onDrop:.  .  .  .  .  .  .  .  .  .  .  .  .  .

Проблема в том, что при использовании нового протокола Transferable и нового View модификатора .dropDestination (for: action: isTargeted:); его параметр for не принимает несколько ТИПов «сбрасываемых» объектов одновременно, как это делает выше приведенный View модификатор .onDrop (of: [.plainText, .url, .image] …).

Читать далее

Протокол Transferable в SwiftUI — передача альтернативного контента с помощью ProxyRepresentation

В предыдущем посте у нас был первый практический опыт работы с протоколом Transferable; новым API, который был представлен на WWDC 2022 и который призван значительно сократить усилия, необходимые с нашей стороны для копирования и вставки (Copy & Paste), a также для перетаскивание и “сброса” (Drag & Drop) данных внутри одного приложения или между разными приложениями.

Если говорить более подробно, то в первом посте продемонстрировано, как перетаскивать и “сбрасывать” (Drag & Drop) объекты пользовательских ТИПов, которые реализуют протокол Codable, при этом было рассмотрено несколько интересных концепций:

  • типы контента (content types UTI) и как декларировать пользовательские UTIs,
  • правильная реализация протокола Transferable, чтобы объекты представленного пользовательского типа UTI можно было перетаскивать,
  • как запустить операцию перетаскивания (Drag) в SwiftUI,
  • как управлять “сбросом” (Drop) перетаскиваемого объекта в SwiftUI.

Этот пост в значительной степени является продолжением предыдущего, поскольку он фокусируется на другой функции протокола Transferable: a именно, как указать дополнительный контент content type для передачи поверх основного перетаскиваемого контента content type.

Читать далее

Протокол Transferable меняет правила игры для Drag & Drop в SwiftUI

На WWDC 2022 среди других интересных анонсов Apple представила новый протокол в Swift под названием Transferable. Transferable позволяет очень легко и просто копировать данные между разными точками в одном и том же приложении или в разных приложениях. И когда речь идет о копировании, это включает не только Copy & Paste (копирование и вставку), но и Drag & Drop (перетаскивание и «сброс»).

До появления протокола Transferable передача данных как между отдельными частями одного приложения, так и между разными приложениями, осуществлялась классом class NSItemProvider. Именно этот класс делает очень сложные вещи, связанные с передачей данных между процессами. Он также решает проблемы с безопасностью и определенно там есть многопоточность, потому что мы не хотим блокировать UI двух приложений в случае передачи между ними больших по объему  изображений. Класс NSItemProvider управлял всем этим вместо нас. К сожалению, класс NSItemProvider — это до-Swift класс, и мы должны использовать “as” как  “мостик” между такими Swift ТИПами как String, и такими ТИПами старого NS Мира, как NSString, это Objective-C вещи, и “as” является “мостом” в этот старый мир. До появления протокола Transferable технология Drag & Drop (перетаскивание и сброс) была одним из таких мест соприкосновения «старого» и нового Миров.

Новый протокол Transferable можно использовать только в операционных системах iOS 16+, macOS 13+ (Ventura и новее), watchOS 9.0+ и tvOS 16 +. Фактически, протокол Transferable заменил NSItemProvider, и для тех, кто хочет программировать операции Copy & Paste (копирование и вставку) и Drag & Drop (перетаскивание и «сброс») он меняет правила игры.

У протокола Transferable есть только одно требование: указать хотя бы одно представления передаваемых данных в static свойстве transferRepresentation:Многие Swift ТИПы уже реализуют протокол Transferable:

  • String
  • Data
  • URL
  • Attributed String
  • Image

Для ваших оригинальных Моделей Данных Apple предоставила API различных представлений передаваемых данных TransferRepresentation, но вы также можете определить и свои собственные представления. Определяемые системой представления передаваемых данных TransferRepresentation охватывают большинство случаев использования. Они включают:

  • CodableRepresentation — Передача данных, описанных структурой, которая реализует протокол Codable
  • DataRepresentation — Передача данных, которые могут быть особым образом закодированы в Data и декодированы из Data.
  • FIleRepresentation — Передача данных путем сохранения информации на диске и передача URL. Apple советует использовать этот тип представления для больших объемов данных.
  • ProxyRepresentation — для передачи альтернативного контента того же самого представления.

В результате протоколу Transferable для Copy & Paste (копирования и вставки) или для Drag & Drop (перетаскивание и «сброса») потребуется всего несколько строк кода для выполнения всей тяжелой работы «за кулисами» для:

  • пользовательских ТИПов, реализующих протокол Codable.
  • данных Data
  • файлов Files

В этом посте мы рассмотрим, как перетаскивать (Drag and Drop) с помощью протокола Transferable данные пользовательского ТИПа, которые реализуют протокол Codable. И лучше всего это показано в статье «Опыт использования протокола Transferable для Drag & Drop в SwiftUI» (First Experience With Transferable Implementing Drag And Drop In SwiftUI), перевод который я и сделаю. Читать далее