Лекция 1. Начало работы со SwiftUI. CS193P Spring 2023.

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

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

Введение

Привет, Интернет, и добро пожаловать на курс CS193P Стэнфордского университета Весна 2023: Разработка приложений для iOS с использованием SwiftUI.

Я —  Пол Хегарти. Я веду этот курс уже 13 или 14 лет, и время от времени я записываю его и выкладываю в Интернет на всеобщее обозрение, чтобы вы могли видеть, чем мы занимаемся здесь, в Стэнфорде.

К сожалению, в этом семестре я этого не сделал. Мы вернулись в кампус после пандемии и я не установил все необходимые камеры для этого, так что прошу прощение за это. Тем не менее, я записал скриншоты экрана своего ноутбука. Мне пришлось это сделать, потому что моим студентам при выполнении Домашних Заданий 1 и 2 необходимо следить за тем, что я делаю, иногда им нужно иметь возможность вернуться назад и увидеть код демо-версии. Это хорошо и для вас, потому что вы можете делать то же самое. Это также очень хорошо для моих основных слайдов, где я рассматриваю некоторые концепции SwiftUI и т. д. А еще я весь семестр держал в ухе этот AirPod, так что, надеюсь, звук во всем этом будет довольно хорошим.

Однако есть вещи, которые вы не увидите на экране. Например, для карточной игры, которую мы разрабатываем на этом курсе, я сделал несколько настоящих карточек. 

Я также довольно часто использую LEGO (Лего) в качестве аналогии SwiftUI. Вы не сможете увидеть мой вертолет LEGO и мою печально известную сумку LEGO … 

… но обязательно услышите о них, когда будете слушать демонстрационные примеры.

А еще у нас есть совершенно замечательный большой проекционный экран, и я обычно ходил перед ним и жестикулировал при написании кода. Очевидно, вы не увидите на видео моих жестов. Надеюсь, что шрифты будут достаточно большими, чтобы вы могли их увидеть на небольшом устройстве, но если нет, то вам, возможно, придется перейти на iPad или что-то в этом духе. Но я думаю, что в целом все будет очень хорошо.

Итак, это университетский курс. Лекции курса CS193P Spring 2023 читаются каждую неделю в течение 10 недель. Мы пытаемся понять не только то, как разработать приложение для iOS, но и то, как работает система SwiftUI в целом?

Читать далее

WWDC 2023. Новый фреймворк SwiftData для управления данными. Эксперименты

SwiftData дебютировал на WWDC 2023 в качестве замены фреймворка Core Data и обеспечивает постоянное хранение данных на Apple устройствах и беспрепятственную синхронизацию с облаком iCloud. Весь API SwiftData построен вокруг современного Swift.

Примечание. SwiftData является частью iOS 17, и на момент написания этой статьи мы имеем версии Xcode 15.0 и iOS 17.0 .

В SwiftData, в отличие от своего предшественника, базы данных Core Data, очень просто создать Схему (или Модель Данных) для постоянного хранения информации в вашем приложении. Для этого прямо в коде создаются обычные Swift классы class со свойствами, имеющими обычные базовые Swift ТИПы, ТИПы или другие Swift классы Схемы. Вы также можете использовать как Optional, так и НЕ-Optional ТИПы.

Чтобы превратить эти обычные Swift классы в постоянно хранимые объекты, Apple дала нам «волшебную палочку» в виде макросов, самым главным из которых является макрос @Model.

Если вы пометите макросом @Model обычные Swift классы, то получите не только постоянно хранимые объекты, но и сделаете их Observable, Hashable и Identifiable, и вам не нужно предпринимать никаких дополнительных усилий при использовали их в SwiftUI, ибо новый в iOS 17 протокол Observable обеспечит вам «живое» отображение на UI всех изменений ваших хранимых объектов, а Identifiable и Hashable позволят беспрепятственное использовать их в списках ForEach.

В SwiftData, в отличие от Core Data, нет никаких внешних файлов для Модели Данных и никакой «закулисной» генерации старых Objective-C классов, которые еще нужно адаптировать для использования в Swift. В SwiftData всё исключительно просто.

Кроме того, в SwiftData существенно, по сравнению с Core Data, упрощена выборка данных и отображение её результатов на UI. Для этого предназначена «обертка свойства» @Query, для которой вы можете указать предикат Predicate (то есть условия выборки данных) и сортировку результата SoreDescriptor. Новый мощный предикат Predicate выгодно отличается от старого предиката NSPredicate Core Data тем, что теперь вы можете задавать условия выборки данных, используя операции самого языка программирования Swift, а не какую-то замысловатую форматированную строку.

SwiftData дополнен такими современными возможностями как Swift многопоточность и макросы. В результате в Swift 5.9 мы получили, по определению самого Apple, “бесшовное” взаимодействие с постоянным хранилищем данных в нашем приложении. SwiftData совершенно естественным образом интегрируется в SwiftUI и прекрасно работает с CloudKit и Widgets.

Если вы начнете работать со SwiftData, то вообще не почувствуете даже «духа» Core Data, всё очень Swifty. Apple настаивает на том, что SwiftData — это совершенно отдельный от Core Data фреймворк, нам точно неизвестно, является ли SwiftData «оболочкой» Core Data, но даже если это так, то она настолько элегантно, интуитивно и мастерски реализована, что у вас будет ощущение работы исключительно в «родной» cреде языка программирования Swift.

В этом посте я покажу вам, как:

  • определить Схему данных в SwiftData, 
  • выполнить CRUD операции (Create — Создать, Read — прочитать, Update — модифицировать, Delete — удалить),;
  • сформировать различные запросы Query к данным с помощью предиката Predicate
  • использовать «живой» запрос @Query в SwiftUI и как его динамически настраивать,
  • эффективно «закачать» JSON данные в SwiftData хранилище без блокировки пользовательского интерфейса (UI).
Читать далее

Как превратить единственный контейнер в 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), перевод который я и сделаю. Читать далее

Лекция 16. Мультиплатформенная версия EmojiArt (macOS). CS193P Весна 2021г.

Это Лекция 16 курса Stanford CS193p, весна 2021 года.

Эта лекция полностью посвящена мультиплатформенной поддержке. В частности, запускается приложение EmojiArt на Mac как собственное приложение для macOS, но при этом вы будете удивлены парой вещей. Одна из них — это какой мощный UI мы создали для работы с документами на Mac на базе документо-ориентированной SwiftUI архитектуры, а также сколько нашего iOS кода практически без модификации работает на Mac. Для той части, которая не работает, применяется несколько стратегических хитростей, способных максимально адаптировать наш код к мультиплатформенности.

На Лекции 16 профессор создает единый проект с большим количеством общего кода, и да,  с некоторыми #if os(…) — else #endif, потому что две платформы, macOS и iOS, это не одно и то же. У них не совсем один и тот же способ взаимодействия с пользователями и т. д., так что он адаптирует код к разным платформам.

Читать далее

Лекция 15. Интеграция с UIKit. CS193P Весна 2021г.

Это Лекция 15 курса Stanford CS193p, весна 2021 года.

Хотя название Лекции «Интеграции с UIKit«, задача этой Лекции — более широкая, заставить работать наше документо-ориентированное приложение EmojiArt на iPhone. До сих пор функциональные возможности приложения EmojiArt в большей степени были ориентированы на iPad. Запуск этого приложения на iPhone показал, что большая часть кода прекрасно адаптируется самой SwiftUI, например, popover на iPad автоматически преобразуется в полноэкранный sheet на iPhone, но не все работает так гладко.

Например, нам пришлось добавить ещё три способа получения фонового изображения background для нашего документа, ибо перетягивание его из Safari, находящегося тут же на экране iPad, в случае iPhone не работает.

Вот эти способы :

  • копирование  и вставка (Copy & Paste) изображения c  Pasteboard
  •  изображение с фотокамеры
  •  изображение из Библиотеки Фотографий (Photo Library)

Для этого мы должны были решить вопрос размещения множества кнопок Button, соответствующих различным способам получения фонового изображения документа, на панели инструментов toolbar, а также вопросы интеграции UIKit API  фотокамеры и Библиотеки Фотографий (Photo Library) в SwiftUI приложение.

Читать далее

Лекция 14. «Документо-ориентированная» архитектура. CS193P Весна 2021г.

Это Лекция 14 курса Stanford CS193p, весна 2021 года.

На этой Лекции профессор подробно, строка за строкой, рассматривает код в главном файле приложения с @main, и очень наглядно демонстрирует, как работает «сцена» Scene. В большинстве случаев вы просто будете использовать одну из двух основных встроенных в SwiftUI “сцен” Scene, которыми являются WindowGroup и DocumentGroup с аргументом newDocument. Есть также DocumentGroup (viewing:) для read-only документов.

WindowGroup и DocumentGroup немного напоминают ForEach для Scenes , но эти ForEach “проходят” не через массив Array “чего-то”. Вместо этого каждая из этих “сцен” Scene создается пользователем либо путем создания «New Window» на Mac, либо путем разделения экрана разными (или одинаковыми) приложениями на iPad. На iPhone создание Scene выполняется только один раз, потому что только одна “сцена” Scene в данный момент находится на iPhone и заполняет собой целый экран. 

WindowGroup — это основной не “документо-ориентированный” встроенный в SwiftUI Scene. Это то, что мы использовали до сих пор в обоих наших приложениях Memorize и EmojiArt.  Внутри WindowGroup мы просто размещаем топовое View, которое хотим видеть на нашем экране. Мы можем делиться нашей ViewModel в виде @StateObject со всеми “сценами” Scene, которые можно создать.

Эти “сцены” Scene немного отличаются для различных платформ и в вашем приложении может быть множество Scenes. Легче всего это представить на Mac, где каждое “окно” — это маленькая “сцена” Scene, все очень, очень просто. На iPhone тоже легко представить, что такое “сцена” Scene, у вас всего одна “сцена” Scene самого верхнего уровня. Но на iPad вы можете даже не осознавать, что там происходит со “сценами» Scenes.

Поэтому профессор не торопится переходить сразу к DocumentGroup. Сначала он дает нам почувствовать в полной мере, что такое «сцена» Scene, оставаясь в пределах WindowGroup и рассматривая попутно такие «Обертки Свойства», как @SceneStorage@AppStorage и @ScaledMetric. Все это сопровождается великолепным демонстрационным примером на iPad. В результате чего мы приходим к тому, что если мы оставим наше приложение EmojiArt в WindowGroup, то всё, на что можно рассчитывать — это автосохранение единственного документа.

Это приводит нас прямо к разговору об архитектуре “документо-ориентированного” приложения в SwiftUI, в котором главным действующим лицом является “документ” наподобие EmojiArt. Конечно, мы хотим, чтобы у каждого документа EmojiArt была своя собственная ViewModel и чтобы каждый документ имел свой отдельный файл. Но кроме этого мы хотим иметь возможность переименовывать, перемещать эти файлы, удалять эти файлы и т.д. и все это в одном приложении.

У SwiftUI есть очень мощный механизм, который заботится обо всем этом вместо нас, и на Лекции 14 профессор учит нас использовать этот механизм, который называется DocumentGroup. Он превращает EmojiArt в многодокументное приложение и позволяет ему работать как “родному” приложению как на iOS, так и на Mac. Для этого не требуется много кода. 

Читать далее

Лекция 13. «Издатель» Publisher. Ещё о «постоянном хранении». Cs193P Весна 2021г.

Это Лекция 13 курса Stanford CS193p, весна 2021 года.

Лекция 13 посвящена 3-м темам:

  • “издатель” Publisher
  •  “постоянное хранения” (persistence) в «облаке» CloudKit
  • “постоянное хранение” (persistence) в локальной базе данных CoreData.

Две бонусные лекции (Enroute) с демонстрацией CoreData были прочитаны весной 2020 года, а не весной 2021 года. Изложенный материал (Picker, использование Codable для извлечения данных из REST API и CoreData) по-прежнему актуален (по крайней мере, на весну 2021 года), поэтому эти лекции также включены сюда. Ссылки представлены в конце этого поста.

“Издатель” Publisher

Первая тема — это “издатели” Publisher, своего рода API, некоторый формализм, для потоковой информации, которая порождается одной частью вашей программы, a потребляется — другой.

“Издатель” Publisher — это лишь один из «игроков» реактивного Swift фреймворка Combine, который оперирует такими абстрактными понятиями, как «издатели» Publishers, «подписчики» Subscribers и операторы Operators. Благодаря тому, что Apple предоставляет разработчикам уже готовых «издателей», «подписчиков» и операторов, код, написанный с помощью Combine, оказывается очень компактным и хорошо читаемым.

Читать далее

Лекция 12. Привязка Binding. «Всплывающие окна» sheet popover Alert. Navigation. EditMode. CS193P Spring 2021.

Это Лекция 12 курса Stanford CS193p, весна 2021 года.

На этой фантастической и очень важной Лекции (она длится почти 2 часа) Пол Хэгерти рассматривает супер важную тему — Property Wrappers (“Обертки Свойства”). Мы наконец-то поймем, что такие вещи как @State, @StateObject, @Published, @ObservedObject и другие @штуковины делают под «капотом». Поэтому сначала эту тему профессор представляет теоретически, то есть на слайдах, а затем следует громадная демонстрация, на которой показывается все это в действии, но не только это, а также много всего другого, что можно изучить только в  демонстрационном пример: текстовые поля TextField, «всплывающие окна» popover и sheet, навигацию с помощью NavigationView, формы Form, списки List, режим редактирования EditMode и т.д, интересные модификаторы .onDelete, .onMove, .onChange.

Property Wrapper (“Обертка Свойства”) — это просто структура struct, в которую встроен некоторый шаблон “поведения”переменной var, которую эта структура struct “оборачивает”.

Например, @State заставляет переменную var “жить” в “куче” (heap), делая её writable в View, то есть дает возможность “писать” в нее новые значения в противоположность обычным переменным var в View, которые являются unwritable (только для чтения).

Мы знаем, что @Published публикует изменения переменной var, которые заставляют Views перерисовывать себя или делать другие подобные вещи.

То же самое происходит с @ObservedObject переменной var, которая отслеживает изменения в вашей ViewModel и заставляет View перерисовывать себя.

Читать далее