Серия «Дневник разработчика»

Навигация в SwiftUI / 4 min

Навигация в SwiftUI / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

В этой статье вы прочитаете:

  • Как открывается попап / модальное окно

  • Как открыть другой экран на примере галереи

  • Как вернуться назад

  • Как открыть экран, связанный с объектом - на примере просмотра фото

Примеры изображений и кода из этой статьи доступны на GitHub

Для примеров я использую NavigationStack, доступный в iOS 16+.

Как открывается попап / модальное окно

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

Для отображения будет использоваться единый флаг AccessLevel. Этот флаг меняется в разных частях приложения через Binding. В случае его обновления сработает обновление состояния основного View приложения и будет отображен блокирующий экранчик проверки возраста.

Навигация в SwiftUI / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера

Как открыть другой экран на примере галереи

Галерея - это экран, который не зависит от конкретного объекта, это просто экран в стеке. Поэтому для его отображения будет использоваться NavigationPath. Если грубо, то это последовательность элементов, которые определяют состояние NavigationStack. Один элемент - один экран.

Навигация в SwiftUI / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Я добавлю enum который будет определять необходимый экран. Для примера в нем будет один элемент - gallery, но такой подход позволяет добавить любое количество необходимых экранов, а associated value в enum позволят добавить данные или настройки для экрана. Данные о необходимом для открытии экране придут в модификатор navigationDestination с указанным enum.

Навигация в SwiftUI / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера

Теперь для отображения экрана галереи необходимо в path через метод append добавить значение gallery.

Навигация в SwiftUI / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера

Как вернуться назад

Чтобы вернуться назад необходимо вызывать метод dismiss у глобального объекта presentationMode, доступного по аналогичному ключу. Я повесил обработчик на левую кнопку верхней панели на экранах галереи и просмотра фото.

Навигация в SwiftUI / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера

Как открыть экран, связанный с объектом - на примере просмотра фото

Экран просмотра фото - это экран, связанный с объектом - фотографией. Этому экрану обязательно надо передать изображение, которое он будет отображать. Самое подходящее место для передачи информации о фотографии - это сетка галереи GalleryGrid, которая обрабатывала выбор фото. Но протаскивать туда ссылку на path будет очень неудобно.

Но этого можно не делать, если использовать NavigationLink. Этот объект существует в двух основных вариантах:

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

  2. В параметрах Оформление и объект для навигации - это то, что я буду использовать для перехода из галереи в экран фотографии

Я добавил в GalleryGrid, созданной в статье про галерею, режим выбора и когда он выключен, галерея работает как переход на просмотр фото. NavigationLink в оverlay с Сolor.clear в качестве оформления будет работать как невидимая кнопка для перехода. NavigationLink отправит объект image в ближайший по иерархии NavigationStack.

Навигация в SwiftUI / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера

В основном коде добавляется обработчик этих объектов через модификатор navigationDestination, который будет обрабатывать объекты MediaItem

Код примера

Я добавил .navigationBarHidden(true), потому что в приложении используются собственные панели и не требуется системная панель с кнопкой назад

Вот как это работает

Заключение

В этой статье я рассказал о том, как создать галерею фотографий, сделать возможность их выбора и добавить переключение размера.

Все работающие примеры есть в проекте на Github, их можно скачать и запустить.

Пишите в комментариях интересующие вас темы для будущих статей, а чтобы их не пропустить - подписывайтесь на дневник.

Показать полностью 6 1

Swift Combine - Часть 2 / 4 min

Это вторая статья о работе со Swift Combine, в ней я расскажу про математические операции, поиск совпадений, операции с элементами по индексам, выбор и объединение потоков. Ссылка на первую статью

Swift Combine - Часть 2 / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

В этой статье вы прочитаете:

  • Операции

  • Math
    Matching
    Sequence
    Select
    Collecting & Republishing

Примеры кода из этой статьи доступны на GitHub

Операции и преобразования

Данные в Combine можно преобразовывать, менять потоки, фильтровать. Все они логически разбиты на блоки. Ниже я опишу каждый блок, входящие в него методы, что они делают и приведу диаграммы, показывающие вход и выход.

У некоторых операций есть try... вариант, который позволяет вернуть ошибку, но я их дополнительно не описывал.

Math

  • .count - подсчитывает количество объекто в возвращает число

  • .max - возращает самый большой объект. Он должен соответствовать Comparable. В примере просто сравниваются числа

  • .max(by ) - возвращает самый большой объект, но сравнение выполняется в блоке. В примере числа красных объектов умножаются на 10

  • .min - возращает самый маленький объект. Он должен соответствовать Comparable. В примере просто сравниваются числа

  • .min(by ) - возвращает самый маленький объект, но сравнение выполняется в блоке. В примере числа красных объектов умножаются на 10

Matching

  • .contains - возвращает bool, есть ли объект в потоке. Он должен соответствовать Equitable. В примерах 1 и 2 ищется синий треугольник

  • .contains(where: ) - возвращает bool, есть ли объект в потоке, но сравнение выполняется в блоке. В примере 3 ищется любой треугольник

  • .allSatisfy - возвращает bool, если все объекты соответствует условию в блоке. В примере ищутся звездочки

Sequence

Swift Combine - Часть 2 / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост
  • .drop(while:) - пропускает все элементы, пока они соответствую условию в блоке. Как только пришел другой объект, ничего больше отбрасываться не будет. В примере 1 и 2 условие - треугольник. Пока они в начале - отбрасываются, а после звезды уже остаются. Позволяет отфильтровать стартовые данные, заголовки и др

  • .drop(untilOutputFrom:) - пропускает все элементы потока, пока другой поток не выдаст сигнал. Например игнорировать таймер, пока не пришел флаг начала анимации

  • .dropFirst - отбрасывает первый элемент в потоке

  • .dropFirst(n) - отбрасывает первые N элементов в потоке

  • .append(...) - добавляет к завершившемуся потоку набор данных в конец. В примере добавляются треугольник, звезда и звездочка

  • .append( pub ) - добавляет к завершившемуся потоку данные из другого потока. В примере красная звездочка игнорируется, т.к. первый поток на тот момент не завершился, а остальные элементы добавляются в конец.

  • .prepend(...) - добавляет в начало потока набор данных. В примере добавляются треугольник, звезда и звездочка

  • .prepend( pub ) - добавляет в начало данные из завершившегося потока

  • .prefix(n) - возвращает первые n элементов потока

  • .prefix(untilOutputFrom:) - возвращает элементы потока, пока второй поток не выдаст сигнал

  • .prefix(while: n) - возвращает элементы потока, пока они соответствуют условиям. После нарушения условия выдача останавливается

Swift Combine - Часть 2 / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Select

Swift Combine - Часть 2 / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост
  • .first - возвращает первый элемент потока

  • .first(where: ) - возвращает первый элемент потока, соответствующий условию в блоке

  • .last - возвращает последний элемент завершившегося потока

  • .last(where: ) - возвращает последний элемент завершившегося потока, соответствующий условию в блоке

  • .output(at:) - возвращает элемент с определенным индексом из потока

  • .output(in:) - возвращает элементы с индексами из диапазона

Collecting & Republishing

Swift Combine - Часть 2 / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост
  • .combineLatest( pub ) { a,b } - комбинирует элемент последнего сигнала потока с последним элементом другого потока. В примере сначала приходят красная звезда в потоке 1 и оранжевая единица в потоке 2, получается их пара. Затем в потоке 1 приходит синяя звезда и она комбинируется с оранжевой единицей в потоке 2. Фиолетовая двойка в потоке 2 приходит в тот же временной слот, но все-таки после синей звезды. И там по очереди Поток 1, Поток 2 приходят и сочетаются с последним сигналом другого потока

  • .combineLatest( pub ) { tuple } - у метода есть другой вариант, который возвращает не два параметра, а один tuple

  • .combineLatest ABC и ABCD - выполняет такие же операции, но сочетая 3 и 4 потока

  • .merge - склеивает несколько поток в один по мере поступления данных

  • .zip - собирает потоки в пары / тройки / четверки в порядке их следования в потоках. Сначала все первые элементы, потом все вторые и т.д. Поэтому на одинаковых данных zip дает 3 пары элементов, а combineLatest - 5

Заключение

В этой статье кратко рассмотрены операции групп Math, Matching, Sequence, Select, Collecting и Republishing с визуализацией. Все визуализации получены в приложении-песочнице.

В последней статье я расскажу про операции для работы с несколькими потоками вместе, тайминги, входы/выходы и работу приложения-песочницы.

Все работающие примеры preview есть в проекте на Github, их можно скачать и запустить.

Пишите в комментариях интересующие вас темы для будущих статей, а чтобы их не пропустить - подписывайтесь на дневник.

Показать полностью 4

Отображение фото / 5 min

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

Отображение фото / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

В этой статье вы прочитаете:

  • Как отображать фотографии в разных режимах

  • Какие есть сложности в работе с просмотром фото в SwiftUI

  • Как реализовать просмотр фото на UIKit и через Prepresentable интегрировать его в SwiftUI

Примеры изображений и кода из этой статьи доступны на GitHub

Отображение фотографий в разных режимах

В приложении фотография отображается в трех режимах:

Просмотр фото - для отображения готового изображения. Оно уже обрезано, наклеены наклейки и применен фильтр.

Отображение фото / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Редактор фото - отображается исходное изображение, которое обрезано и к нему применен фильтр. Наклейки отображаются поверх в виде самостоятельных View, которые можно перемещать. Про наклейки будет отдельный пост.

Отображение фото / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

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

Отображение фото / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Во всех трех режимах изображение можно масштабировать двумя пальцами или слайдером и двигать. Режим аналогичен системному приложению "Фото".

Про добавление стикеров и работу рамки для обрезки я расскажу в будущих статьях, в этой будет только просмотр.

Какие есть сложности в работе с просмотром фото в SwiftUI

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

Основные проблемы:

  1. ScrollView в SwiftUI не умеет из коробки работать с Zoom. Его можно эмулировать через ScaleEffect и MagnificationGesture(). Однако смещение будет сбиваться, т.к. жест масштабирования не возвращает свои стартовые точки

  2. Аналогичная проблема будет с вращением. От жеста можно получить угол поворота, но не центр

  3. Тяжело развести жесты обрезки и скролла. Если привязать жест к "ручке" изменения размера, то при резком перемещении он будет "отваливаться" и перемещение будет сбрасываться. И я не смог привязать жест к контейнеру и фильтровать его работу в зависимости от того, где был начат жест.

Я решил, что эти экраны мне будет легче создать на базе UIKit и добавить в SwiftUI как готовый компонент. Возможно в будущем, когда будет больше опыта и подходящих API, я смогу реализовать все элементы на SwiftUI.

Результат опытов со SwiftUI можно посмотреть в Github

Просмотр фото на UIKit и обертка в Representative

Я создаю общий компонент, который реализует все три режима просмотра. Основная цель в том, чтобы получить бесшовное переключение между режимами просмотра.

Код готового компонента можно найти на Github

Как будет устроен компонент:

Отображение фото / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост
  • Scroll View - будет выполнять Zoom и Scroll

  • Container View - будет делать обрезку фото в режиме редактирования и обеспечивать зону для вращения фото в режиме обрезки и поворота. Внутри ScrollView, является ViewForZooming

  • Image View - будет отображать изображение, обрезанное или оригинальное. Центром привязано к Container View. В режиме редактирования смещается так, чтобы видимая в Container View часть совпадала с CropZoneView. В режиме редактирования и обрезки поворачивается на заданный угол.

  • CropZoneView - виртуальная View для отладки. В режиме обрезки рамка обрезки совпадает с CropZoneView, но отрисовывается в глобальных координатах, чтобы ручки изменения размера были одного размера независимо от масштаба.

Какие функции будет реализовывать компонент:

Скролл с отступами и центрированием

Если изображение больше, чем ScrollView, то к изображению будут добавлены фиксированные отступы, чтобы с ним было удобнее работать и просматривать. Если размер изображения меньше, чем ScrollView, к изображению будут добавлены плавающие отступы, чтобы его центрировать.

Для этого необходимо управлять параметром ContentOffset

Отображение фото / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примера выше

Расположение фото фото и поворот

Для режимов редактора фото и обрезки необходимо повернуть фотографию, для этого она будет центром привязана к контейнеру внутри ScrollView, И с помощью transform будет повернута на необходимый угол. Изображение в режиме просмотра будет работать также, только у него угол всегда равен нулю

Отображение фото / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примера выше

Обрезка фото

В режиме редактора будет использоваться исходная фотография, но она будет обрезана контейнером так, чтобы края были не видны. Для этого в контейнере будет проставлен clipsToBounds, сам он будет иметь размер конечной фотографии, поэтому края будут обрезаны. В режиме обрезки контейнер будет иметь размер квадрата со стороной, равной диагонали изображения

Сохранение данных об обрезке и повороте фотографии

Обрезанная и повернутая фотография получается из оригинальной с помощью серии операций:

  1. Сдвиг центра - сохраняется как Crop Center

  2. Новый размер - сохраняется как Crop Size

  3. Поворот - В приложении для детей есть ползунок с диапазоном поворота -45...45 градусов и кнопка вращения на 90 градусов. Поэтому хранятся два значения, малый угол и квадрант нуля

Все размеры хранятся в виде относительных значений. За 1 берется диагональ изображения. Сдвиг центра отсчитывается от центра изображения.

Отображение фото / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Использование Representable

Чтобы SwiftUI работал с UIKit не вдаваясь в детали, создается UIViewRepresentable или UIViewControllerReplresentable.

У меня ViewController, поэтому дальше будет про него, но для UIVIew работает точно так же. Его код можно найти на GitHub

Этот прокси объект отвечает за то, чтобы создать View или ViewController, положить его в правильное место иерархии View и обработать передачу состояния в обе стороны.

View создается в методе makeUIViewController. Необходимо вернуть настроенный ViewController

У этого объекта создаются свойства SwiftUI: @State, @Bindable и др. Изменение эти свойств приводит к вызову метода updateUIViewController, который позволяет обновить свойства у UIKit ViewController.

Для обновления данных в обратную сторону создается объект, который называется Coordinator. В нем можно разместить все Binding переменные и передать сам объект во ViewController, чтобы обновлять значения.

При изменений значений внутри ViewController, необходимо новые значения сохранить в wrappedValue Binding координатора. Тогда SwiftUI пробросит их всем подпищикам изменений

Заключение

В этой статье я рассказал о том, как решил не мучаться себя и вместо чистого SwiftUI с костылями использовал UIKit через Representable.

Все работающие примеры есть в проекте на Github, их можно скачать и запустить.

Пишите в комментариях интересующие вас темы для будущих статей, а чтобы их не пропустить - подписывайтесь на дневник.

Показать полностью 7

Swift Combine - Часть 1 / 5 min

Это первая статья о работе со Swift Combine. Из неё вы узнаете о реактивном программировании и операциях преобразования, фильтрации и свертки сигналов.

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

В этой статье вы прочитаете:

  • Что такое Combine и реактивное программирование

  • Операции: Mapping, Filtering, Reducing

Примеры кода из этой статьи доступны на GitHub

Что такое Combine и реактивное программирование

Swift Combine - это интегрированная в язык Swift реализация реактивного программирования.

Я не буду углубляться в теорию программирования, а опишу отличия с точки зрения отличий в коде для разработчика. Для примера возьмем строку поиска, в которой пользователь вводит данные и в блоке ниже будет выведен список подходящих объектов.

Пусть для простоты будут следующие операции:

  1. Получение ввода от пользователя, фильтрация 3х символов

  2. Формирование сетевого запроса для сервера и отправка запроса

  3. Расшифровка ответа

  4. Вывод результатов, сохраняя данные в переменную

В объектно-ориентированном императивном подходе на базе MVC у ViewController будет примерно так(Написал из головы):

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

В случае с реактивным программированием, это будет выглядеть примерно так:

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Получается поток обработчиков, в которых нет "висячих" результатов работы. И прямой поток данных, и ветка ошибок должны иметь свой обработчик, иначе приложение не скомпилируется.

В целом плюсы реактивного подхода:

  1. Однозначность обработки положительных и отрицательных исходов

  2. Фильтрация событий по времени. В примере из всех вводов пользователя за 0.3 секунды будет взят последний

  3. Возможность собирать несколько потоков и разделять на несколько потоков. Например, можно отправить запрос к кэшу на диске и сети и взять тот, что вернется быстрее без ошибки

  4. Удобная связка со SwiftUI и Async благодаря тому, что Combine - часть языка, а не внешняя библиотека. Большая часть системных операций и UI также готова к работе с Combine

Источники данных и потребители

Эту тему я рассмотрю подробнее в будущей статье, но кратко опишу.

В Combine источники данных - это Publisher и дочерние типы. У основных системных вызовов, например у сетевых инструментов, SwiftUI и др. есть ответы и события в формате Publisher.

Результат работы можно назначить в свойство через метод assign или обработать как callback в методе sink. Они возвращают объект Cancellable, который надо сохрнить в переменной напрямую или через метод store, иначе память освободится и обработчик не будет работать.

Песочница для реактивных операций

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Для демонстрации работы операций и преобразований я сделал песочницу, в которой можно посмотреть, что подается на вход и что получается на выходе с помощью диаграмм. Про неё так же будет в будущих постах.

Песочница доступа в Github со всеми примерами, включая будущие статьи

Операции и преобразования

Данные в Combine можно преобразовывать, менять потоки, фильтровать. Все они логически разбиты на блоки. Ниже я опишу каждый блок, входящие в него методы, что они делают и приведу диаграммы, показывающие вход и выход.

У некоторых операций есть try... вариант, который позволяет вернуть ошибку, но я их дополнительно не описывал.

Mapping

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примеров

  • .map - преобразование данных, количество на входе равно количеству на выходе. Можно вернуть объект того же типа или поменять тип. Аналогично методу map массива. В примере звезды заменяются треугольниками.

  • .mapError - преобразование ошибок, ошибка на входе - ошибка на выходе. Позволяет свести все ошибки к одному типу для обработки в конце. Обработка в блоке. В примере ошибка меняется на зеленую ошибку.

  • .replaceNil - замена nil на фиксированный объект. В примере nil заменяется оранжевой звездой

  • .scan - похоже на map, но результат передается как второй параметр в блок обработки. На первый шаг подается первый аргумент вызова scan. В примере подается объект-образец, в цвет которого будут покрашены все элементы

  • .setFailureType - позволяет поменять тип ошибки для синхронизации типов. Never поменять на свой тип. В примере в коде сначала ошибка приводится к Never с помощью Catch, а затем обратно к DemoError с помощью setFailureType.

  • .map Keypath - аналогично map, но можно передать 1 keypath, чтобы получить на выходе значение. Если передать 2 или 3 keypath, будет tuple. В примере 1 keypath .color используется для создания результатов, а в примере 2 .color и .icon используются для создания копий в результатах

Filtering

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примеров

  • .filter - аналогично методу над массивом, возвращает только подходящие элементы. В примере не пропускает треугольники

  • .compactMap - аналгично map, но nil пропускается. В примере для сравнения map и compactMap на тех же данных

  • .removeDuplicates - удаляет одинаковые, идущие подряд элементы. Для этого они должны соответствовать протоколу Equitable. В примере синий и синяя звезда - разные.

  • .removeDuplicates(by: ) - удаляет одинаковые, идущие подряд элементы, определяя "одинаковость" в блоке. В примере одинаковость определяется оп цвету, В примере синий и синяя звезда - одно и то же.

  • .replaceEmpty - если поток закончился и ничего не вернул, то можно задать какое-то значение. В примере 1 возвращается оранжевый, а во втором были данные - возвращаются они

  • .replaceError - возвращает определенные данные, если была ошибка. В отличие от catch нет возможности обработать ошибку, она заменяется одним значением на входе

Reducing

Swift Combine - Часть 1 / 5 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примеров

  • .collect - собирает объекты в массив. В примере три цветных объекта становится одним массивом

  • .collect(n) - собирает объекты в массив по несколько штук. В примере 6 объектов собираются в две кучки по три. В примере два в кучки 4 и 2

  • .collect(.byTime ) - собирает объекты по несколько штук, пришедших в указанный интервал

  • .collect(.byTimeOrCount ) - также собирает объекты по несколько штук за указанный интервал, но не больше указанного количества

  • .ignoreOutput - гинорирует то, что было в выдаче. Вернет только сигнал окончания потока или ошибку

  • .reduce - работет как scan, но на выходе будет только 1 объект, последний. В примере суммируются числа на объектах

Заключение

В этой статье кратко рассказано что такое Swift Combine и основные операции преобразования, фильтрации и свертки сигналов с визуализацией. Все визуализации получены в песочнице.

В будущих статьях я расскажу про операции для работы с несколькими потоками вместе, про входы/выходы и примеры использования.

Все работающие примеры preview есть в проекте на Github, их можно скачать и запустить.

Пишите в комментариях интересующие вас темы для будущих статей, а чтобы их не пропустить - подписывайтесь на дневник.

Показать полностью 6

Галерея фотографий / 5min

Из этой статьи вы узнаете как создать галерею для просмотра фотографий с динамическим размером ячеек и выбором фотографий

Галерея фотографий / 5min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

В этой статье вы прочитаете:

  • Как создать фото-галерею

  • Как реализовать выбор фотографий

  • Как сделать переключение размера фото

Примеры изображений и кода из этой статьи доступны на GitHub

Как создать фото-галерею

Чтобы создать фото-галерею необходимо 3 компонента:

  1. Источник фотографий

  2. Шаблон фотографии

  3. Лента для вывода фотографий

Источник фотографий

Под источником фотографий я подразумеваю объект, который будет сообщать ленте о том, сколько есть фотографий и принимать событие о том, что ленту докрутили до конца и нужно загрузить ещё фотографий.

💡 Добавление/удаление фотографий будет описано в одной из будущих статей

Сами фотографии могут загружаться через Core Data, Swift Data, из сети по API. Мой проект использует Realm. Для примера приложение будет загружать фотографии из временной папку, куда будут скопированы ресурсы.

Источник фотографий - это объект типа ObservableObject - специальный тип объекта, который при изменении свойств, помеченных как published будет вызывать обновление состояния View.

Потребуется два таких:

  1. Массив фотографий

  2. Состояние активной загрузки

У загрузки будет три состояния:

  • Есть контент для загрузки

  • Активная загрузка

  • Все загружено

Галерея фотографий / 5min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Сама фотография должна хранить в себе ссылку на изображение, а также флаг качества фотографии. Также будет необходим уникальный ID для вывода списка фото в галерее и для выбора фотографий

Галерея фотографий / 5min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Загрузка фотографий

Также потребуется метод loadMore, который будет вызываться для дозагрузки фото. Этот метод будет вызываться при скролле галереи.

Загрузка фотографий будет выполняться пачками. Загружать фото будет выполнять объект, который будет реализует протокол ContentSource. В метод загрузки будет передаваться последняя фотография в ленте и размер пачки.

💡 Это позволит переключаться между разными хранилищами, я использую Realm и отладочные хранилища с разными фотографиями.

Сам метод loadMore будет выставлять режим загрузки, получать новые фотографии и анализировать. Если пусто, то будет выставляться режим done, иначе - hasMore

Галерея фотографий / 5min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

💡Здесь нет обработки ошибок, чтобы не загромождать пример

Автоматическая загрузка

Автоматическая загрузка будет осуществляться аналогично примеру с исчезающими боковыми кнопками. Потребуется два GeometryReader:

  • Один будет определять высоту контейнера галереи

  • Другой будет следить за расстоянием верха загруженных фотографий от края

Галерея фотографий / 5min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Разница этих двух значений больше нуля будет означать, что нижняя граница фотографий находится выше нижнего края экрана. Если при этом галерея находится в режиме hasMore, можно загрузить ещё фотографий.

Галерея фотографий / 5min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Шаблон фотографии

Шаблон фотографии - это ZStack, в котором будет находиться изображение, а также в углах будут находиться индикаторы для выбора фото и качества фотографии

Галерея фотографий / 5min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Фотографии большие, поэтому загружать их целиком при каждом отображении ленты будет слишком накладно - будет нужна загрузка, масштабирование и обрезка. Чтобы этого избежать необходимо кэширование изображений.

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

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

В приложении будет 3 размера фотографий, поэтому необходимо будет передавать размер фотографии в шаблон, чтобы кэш корректно работал. Также параметром будет флаг того, что фотография выбрана.

Галерея фотографий / 5min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Лента для вывода фотографий

Для отображения галереи потребуется ScrollView, внутри которого будут LazyVGrid и индикатор загрузки. Внутри галереи фотографии будут выводиться через ForEach для массива.

Необходимо добавить GeometryReader для определения размера фотографии.

Галерея фотографий / 5min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Как реализовать выбор фотографий

Для выбора фото необходимо создать массив с ID фотографий. Галерея будет проверять наличие ID фотографии в этом массиве и выставлять флаг Selected. Нажатие на фотографию буде добавлять и удалять ID в массив.

Галерея фотографий / 5min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

💡 Кнопка выбора фотографии добавляется как overlay. Чтобы она была прозрачной в её label добавляется Color.clear, но для того, чтобы её можно было нажать, необходимо ей задать "тело" прямоугольной формы с помощью contentShape и clipShape

Переход на просмотр фотографии и включение режима редактирования будут в одной из будущих статей.

Как сделать переключение размера фото

В галерее будет три режима вывода фотографии, мелкие, средние и крупные фотографии. Если взять средний iPhone, например 14, то на экране будет 5, 3 и 1 соответственно. Конечно выводить в ленте по 1 фото на экран iPad Pro будет слишком, поэтому на планшетах будет примерно такой же размер фотографий.

Галерея фотографий / 5min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

LazyVGrid позволяет задать автоматическое определение количества колонок для размеров ячеек в границах размера. Необходимо создать enum с тремя параметрами размера фото, а также массив размеров фотографий.

Галерея фотографий / 5min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Изменять размер фотографии будут три кнопки внизу галереи, они будут созданы аналогично кнопкам панелей, но будет дополнительный слой, отображающий текущую выбранную кнопку.

Галерея фотографий / 5min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Выглядеть и работать переключение будет так

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

Заключение

В этой статье я рассказал о том, как создать галерею фотографий, сделать возможность их выбора и добавить переключение размера.

Все работающие примеры есть в проекте на Github, их можно скачать и запустить.

Пишите в комментариях интересующие вас темы для будущих статей, а чтобы их не пропустить - подписывайтесь на дневник.

Показать полностью 12 1

SwiftUI View Builder / 4 min

В этой статье я расскажу про то, как работает создание интерфейса в SwiftUI при помощи ViewBuilder

SwiftUI View Builder / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

В этой статье вы прочитаете:

  • Почему у ViewBuilder такой формат

  • Как создать свой контейнер для View

  • 3 способа преобразовать результат ViewBuilder в массив

Примеры изображений и кода из этой статьи доступны на GitHub

Почему у ViewBuilder такой формат

Если посмотреть на протокол View, то видно, что body - это переменная, которая возвращает объект, реализующий протокол View, который должен в итоге получить конкретный тип. И следующий код будет прекрасно работать. Следующий код будет работать

SwiftUI View Builder / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примера выше

Но Apple стремится упростить синтаксис для SwiftUI, поэтому у body есть два префикса - ViewBuilder и MainActor.

ViewBuilder - это обертка вокруг блока, который создает View. Он дает возможность вернуть несколько View, описывая их подряд, по строчкам, не используя дополнительные артефакты в виде массивов.

На выходе создается объект типа AnyView<TupleView<(...)>>. Т.е. следующие два блока кода эквивалентны

SwiftUI View Builder / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примера выше

Как создать свой контейнер для View

Для того, чтобы создать вертикальный стек с общим стилем элементов, который можно переиспользовать(с фоном, границей, тенью) можно создать свой контейнер и применить стили ко всем внутренним элементам, включая Spacer

Для этого необходимо объявить Generic структуру, в которой параметром шаблона будет Content типа View. Необходимо объявить свойство content с типом ViewBuilder, которое будет блоком-фабрикой.

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

SwiftUI View Builder / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примера выше

Также Group можно использовать, чтобы применить какой-то стиль к View, созданному по условию

SwiftUI View Builder / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

ViewExtractor и как преобразовать содержимое ViewBuilder в массив

Увы, способ выше не позволяет каким-то образом изменить порядок дочерних элементов, добавить между ними какие-то объекты или использовать какое-то форматирование, зависящее от количества элементов.

Первый путь

Библиотека ViewExtractor. Она дает достаточно простой синтаксис

SwiftUI View Builder / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

В ней используется тайное знание о том, как устроен SwiftUI, _VariadicView.Tree хранит к себе элементы из ViewBuilder. Но это использование приватного API и Apple может заблокировать такое приложение или оно может сломаться после обновления операционной ситсемы.

Второй путь

Манипуляции c памятью в расширении над ExtensionView. Суть в том, чтобы взять из TupleView дочерние объекты и сказать, что эта область памяти - массив.

SwiftUI View Builder / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примера выше

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

SwiftUI View Builder / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примера выше

Из-за того, что ViewBuilder все оборачивает в AnyView, content[index] не сможет использоваться для сравнения типа, но index и количество объектов позволяют добавить разделители, если необходимо.

Третий путь

Можно создать контейнер, который будет принимать на вход массив элементов определенного протокола.

Плюсы

  • Можно сортировать и фильтровать объекты

  • Сохраняется информация о типах и можно изменять отдельные параметры

  • Доступен индекс объекта и их количество

Минусы

  • Синтаксис требует квадратных скобок и запятых

  • Каждый класс должен соответствовать протоколу через extension

  • Иногда ломается компилятор и выдает не ту ошибку, которая случилась

SwiftUI View Builder / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примера выше

Придется отказаться от синтаксиса ViewBuilder, но такой способ не отвалится внезапно от обновления iOS и даст возможность сортировать и фильтровать объекты, а также обращаться к их типам.

SwiftUI View Builder / 4 min IT, Дневник, Научпоп, Swift, Программирование, Длиннопост

Код примера выше

Заключение

В этой статье разобрана суть ViewBuilder, почему он такой, как создать свой контейнер и как превратить вывод ViewBuilder в массив тремя способами.

Все работающие примеры preview есть в проекте на Github, их можно скачать и запустить.

Пишите в комментариях интересующие вас темы для будущих статей, а чтобы их не пропустить - подписывайтесь на дневник.

Показать полностью 9

Родительский контроль / 4 мин

Статья о том, как сделать родительский режим во всем приложении с проверкой возраста на базе простых математических примеров.

Родительский контроль / 4 мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Из этой статьи вы узнаете:

  • Как и зачем проверять возраст

  • Как создать состояние для всего приложения и управлять им

  • Как создать всплывающее модальное окно проверки возраста

  • Как создать простые случайные примеры проверки возраста

Исходный код всех примеров для этой главы доступен на Git.

Этот подход можно использовать для Premium подписки приложения, а вместе проверки возраста показывать PayWall.

Как и зачем проверять возраст

В мобильном приложении есть несколько разделов и функций, которыми должны пользоваться только взрослые:

  • Отправка, удаление и сохранение фото в галерею

  • Оформление подписки

В соответствии с пунктом 1.3 Kids Category документа о правилах аудита приложений App Store Review Guidelines для доступа в эти разделы необходимо создать Parental Gate

Самые популярные способы проверки возраста: простые арифметические операции, операции с фигурами, но бывают и забавные, например "Что делает видеомагнитофон?" с вариантами ответов.

💡 В моём приложении я буду использовать задачи на арифметику. Сколько будет 4+9, 4x4 или 25/5 с тремя вариантами ответов.

Как создать состояние для всего приложения и управлять им

Родительский режим включается по всему приложению, это состояние будет храниться на уровне приложения. И транслироваться по цепочке как Binding, чтобы можно было добавить переключающие кнопки и обновлять состояние приложения.

Есть два подхода к управлению приложением:

  1. Добавить отдельное состояние детский режим - родительский режим и отдельно управлять состоянием "Запрос разрешения"
    Плюсы - четкость разделения назначения каждого флага
    Минусы - нужно разбираться с 4 состояниями приложения в обработчиках кнопок

  2. Создать три состояния детский режим - запрос разрешения - родительский режим
    Плюсы
    - четкая последовательность смены состояний
    Минусы - нельзя проверить возраст без перехода в родительский режим

💡 Какой вариант выбрать? Я выберу вариант с тремя состояниями. Мне важно, чтобы родительский режим всегда выделялся цветом, а четкость назначения флагов в таком маленьком проекте не важна

Состояние будет храниться в enum, который будет работать как конечный автомат.
Метод next будет переключать состояния по кругу:
Детский режим -> Проверка возраста -> Родительский режим.

Метод cancel возвращает состояние из Проверка возраста в Детский режим. Другие состояния не меняются.

Родительский контроль / 4 мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Про переключение фонов кнопок и панелей я рассказывал в предыдущих статьях. Код выглядит так:

Родительский контроль / 4 мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Как создать всплывающее модальное окно проверки возраста

Отображение панели будет завязано на состояние запроса возраста

Родительский контроль / 4 мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера

Панель проверки возраста появляется модально - т.е. перекрывает целиком весь экран приложения и блокирует работу с ним, пока не будет выбран один из ответов.

Родительский контроль / 4 мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Для отображения панели требуется 6 параметров: A, Оператор, Б и три ответа. Я разделил пример на три части, чтобы можно было знак операции сделать крупнее.

Отображаться панель будет если состояние приложения меняется на "Запрос разрешения". Эта же кнопка будет возвращать детский режим.

Родительский контроль / 4 мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера

А сворачиваться панель будет по тапу на фон или нажатию на кнопку ответа. Правильный ответ продвинет состояние вперед методом next, а неправильный или тап на фон - cancel.

Родительский контроль / 4 мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера

Как создать простые случайные примеры проверки возраста

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

Если взять любые случайные натуральные числа, их сложение и умножение всегда будет натуральным числом. Но с делением и вычитанием это не всегда так. Поэтому для создания примеров на вычитание/деление будет создаваться пример на сложение/умножение и инвертироваться - менять местами ответ и А.

Алгоритм создания операции такой:

Родительский контроль / 4 мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код реализации

Создавать новый пример необходимо при отображении окна. При этом важно помнить, что SwiftUI может обновлять состояние View и пересоздавать всю структуру в случае внешних событий. Поэтому создавать пример необходимо в модификаторе onAppear панели.

Родительский контроль / 4 мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера

При отображении будет создан новый пример, его значения будут присвоены необходимым полям ввода и при нажатии на кнопки будет определяться, правильный ли ответ. Если да - переход в родительский режим, если нет - обратно в детский.

Заключение

В этой статье я рассказал о том, как добавить в приложении специальный режим для всего приложения. Он применим не только для родительского режима, но может использоваться и для Premium раздела приложения.

Все работающие примеры есть в проекте на Github, их можно скачать и запустить.

Пишите в комментариях интересующие вас темы для будущих статей, а чтобы их не пропустить - подписывайтесь на дневник.

Показать полностью 8 1

SiwftUI Preview / 5мин

Из этой статьи вы узнаете, как использовать SwiftUI Preview для идеальной верстки на всех устройствах, темной/светлой теме и что что нового в Xcode 15

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

В этой статье вы прочитаете:

  • Что такое Preview и как работает

  • Ориентация экрана

  • Темная/светлая тема

  • Тестирование на устройствах

  • Проверка элемента на разных размерах и режим Selectable

  • State и смена состояния

Примеры изображений и кода из этой статьи доступны на GitHub.

Что такое Preview и как работает

Режим предварительного просмотра пришел на замену WYSIWYG редактору Storyboards и Nib-файлов. Он позволяет использовать то, что называется Live-coding - изменения в коде вызывают автоматическую пересборку проекта и обновление экрана. Это позволяет легко экспериментировать и пробовать разные подходы на узких местах. Процесс выглядит так:

Чтобы создать Preview есть два подхода, "классический" и "новый"

Классический подход

Он появился вместе со SwiftUI. И работает как в 14, так и в 15 версиях Xcode.

Создается структура, которая реализует протокол PreviewProvider(дальше я для краткости буду писать "Структура типа PreviewProvider"), в её свойстве previews опысывается то, что необходимо отобразить. Все, что вернет это свойство Xcode покажет в Canvas - это дополнительное окно, которое отображается рядом с кодом справа или снизу от окна с кодом.

Если в свойстве вернуть несколько View - они будут отображены как отдельные варианты. Каждому можно задать свое имя через модификатор previewDisplayName

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Макрос #Preview

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Он появился в Xcode 15 и работает только для сборки под iOS 17.

В простом варианте он экономит всего пару строк кода. Название Preview задается первым параметром макроса, другими параметрами можно задать параметры отображения - цветовой режим, ориентацию и др - о них будет ниже.

Скорее всего, этот вариант станет основным для работы с Vision Pro и будет расширяться в дальнейшнем.

Максим Гришутин написал статью на Habr с разбором того, что внутри делает макрос

Что Preview позволяет из коробки:

  • Взаимодействовать с компонентами - нажимать, вводить текст

  • Применить темную/светлую тему

  • Проверить работу при разных ориентациях устройства

  • Посмотреть на работу приложения с разными размерами текста

  • Проверить работу на разных устройствах(Xcode 15)

Все это можно использовать с помощью кнопок внизу в окне предварительного просмотра

В Xcode 15 есть ряд изменений по работе с Preview:

  1. Появился выбор устройства для просмотра, включая Очки Vision Pro

  2. Предпросмотр можно добавить через макрос #Preview, это немного экономит код

Увы, скролл по прежнему ломается, если сделать масштаб покрупнее.

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Ориентация экрана

Сменить ориентацию экрана можно в окне просмотра. Для этого нужно нажать на кнопку Device Settings и включить Orientation, после чего выбрать требуемое значение.

Можно одновременно посмотреть все варианты ориентации, если выбрать Variants и Orientation Variants

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Другой способ - это использовать модификатор .previewInterfaceOrientation в код preview. Но он работает только внутри структуры типа PreviewProvider

В макросе #Preview необходимо использовать параметр traits, модификатор не работает.

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Темная/светлая тема

Сменить темную/светлую тему можно аналогично в окне просмотра. Для этого нужно нажать на кнопку Device Settings и включить Color Scheme, после чего выбрать требуемое значение.

Можно одновременно посмотреть светлую и темную темы, если выбрать Variants и Color Scheme Variants

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Другой способ - это использовать модификатор .preferredColorScheme в коде preview. Она работает и в макросе, и в структуре.

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Тестирование на устройствах

Xcode 15 позволяет выбрать устройств для теста с помощью новой кнопки выбора устройства. И посмотреть на работу приложения на каждом из устройств.

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

В структуре типа PreviewProvider можно указать устройство через модификатор .previewDevice(...) но в качестве параметра нужно указать строку с именем устройства. Узнать его можно в меню запуска или списке симуляторов. Вот типичные устройства, которые установлены по-умолчанию:

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Список устройств:
iPhone SE (3rd generation)
iPhone 14
iPhone 14 Plus
iPhone 14 Pro
iPhone 14 Pro Max
iPad mini (6th generation)
iPad (10th generation)
iPad Air (5th generation)
iPad Pro (11-inch) (4th generation)
iPad Pro (12.9-inch) (6th generation)

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

В макросе #Preview выбор устройства не работает. А для PreviewProvider необходимо выбирать тип устройства Automatic. Но в Xcode 15 предпросмотр устройств пока часто ломается.

Физическое устройство

Проверить работу можно на собственном устройстве, для этого на нем должно быть установлено приложение Xcode Previews, оно должно быть запущено, телефон подключен к маку. В Xcode 15 выбор осуществляется в меню устройства, а в 14 версии есть отдельная кнопка Preview On Device

Этот вариант позволяет удобно тестировать multitouch-сценарии и физические возможности, например доступ к камере

Проверка элемента на разных размерах и режим Selectable

Верстка таких элементов, как ячейки может потребовать проверку размера отдельных элементов, а не экрана целиком. Это работает в режиме Selectable

Это можно сделать с помощью задания traits в макросе #Preview или использования модификатора .previewLayout(). В оба можно передать одно из двух значений.

  • .fixed(width: 200, height: 300) - задает размер для контейнера Preview

  • .sizeThatFits - масштабирует размер контейнера Preview так, чтобы уместить контент.

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Режим Selectable не позволяет взаимодействовать с элементами, но позволяет задать размер контейнера и выбрать элемент в preview и Xcode выделит код, который за него отвечает.

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

State и смена состояния

Ещё один вариант использования Preview - проверка управления состоянием и переключением. Я буду использовать это в следующей статье, посвященной родительскому режиму приложения.

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Использовать @State напрямую ни в структуре типа PreviewProvider, ни в макросе #Preview для проверки изменяемого состояния не получится. Т.к. оба состояния статические, они не обновляются и будут константами. Разницы между static var и .const - нет

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Самый простой и наглядный вариант - это использовать дополнительный контейнер, который будет настоящей View, хранить необходимые состояния и применить Preview уже к нему. Для примера я доработаю код из видео про LiveCoding, чтобы пароль и его видимость можно было задавать извне.

SiwftUI Preview / 5мин IT, Дневник, Научпоп, Swift, Программирование, Длиннопост, Видео, Без звука

Код примера выше

Заключение

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

Расширить возможности позволяют имена для конфигураций и служебные View, которые могут хранить состояние и задавать необходимые данные.

Все работающие примеры preview есть в проекте на Github, их можно скачать и запустить.

Пишите в комментариях интересующие вас темы для будущих статей, а чтобы их не пропустить - подписывайтесь на дневник.

Показать полностью 15 1
Отличная работа, все прочитано!