Pull to refresh

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

Reading time4 min
Views1.1K
EmojiArt -  приложение Drag & Drop из Стэнфордского курса
EmojiArt - приложение Drag & Drop из Стэнфордского курса

Введение

Новый протокол 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ТИПов в один контейнер.

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

Обработка NSItemProvider объектов
Обработка NSItemProvider объектов

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

Поэтому я попыталась добавить три модификатора .dropDestination с тремя различными ТИПами: String.selfURL.selfи Data.self:

Три последовательных модификатора .dropDestination
Три последовательных модификатора .dropDestination

Однако, если я "сбрасываю" String, то получаю зеленый значок +, как и ожидалось, и сбрасывание выполняется успешно. Но когда я перетаскиваю URL или изображение Data, соответствующие второму и третьему ТИПам, я вижу значок на запрет этого перетаскивания. Следовательно, последовательность модификаторов .dropDestination не работает.

Решение

Но можно выполнять “сброс” нескольких ТИПов  в одном .dropDestination, если создать перечисление enumDropItem для представления различных ТИПов данных, которые нужно “сбросить”:

перечисление enum DropItem для нескольких ТИПов "сбрасываемых" объектов
перечисление enum DropItem для нескольких ТИПов "сбрасываемых" объектов

При реализации протокола  Transferable мы использовали ProxyRepresentation в обязательном для этого протокола static свойстве transferRepresentation и, конечно, определили пользовательский UTI с помощью расширения extension класса UTType.

Затем в нашем ZStack мы просто сообщаем модификатору .dropDestination, чтобы он принимал элементы ТИПа DropItem.self:

Модификатор .dropDestination принимает элементы одного ТИПа DropItem
Модификатор .dropDestination принимает элементы одного ТИПа DropItem

А внутри .dropDestination мы разбираемся, что нужно делать в зависимости от того, какой вариант (case) имеет место при "сбросе":

Внутри модификатора .dropDestination разбираемся с вариантами перечисления DropItem
Внутри модификатора .dropDestination разбираемся с вариантами перечисления DropItem

Если вы и дальше собираетесь работать только с версиями iOS 16+, то можно убрать расширение extension массива Array c NSProvider элементами, которое предоставил в наше распоряжение профессор Пол Хэгерти из Стэнфорда для удобства работы с классом NSItemProvider, так как мы заменили класс NSItemProvider на  протокол Transferable. Если же ваше приложение работает с версиями ниже iOS 16, то это расширение extension может быть незаменимым при работе с таким непростым классом, как class NSItemProvider.

расширение extension класса class NSItemProvider
расширение extension класса class NSItemProvider

Для эмоджи, которые мы перетаскиваем и "бросаем" (Drag & Drop) на нашу "картину" не откуда-то извне, а с тематических палитр внутри нашего приложения, мы должны заменить View модификатор .onDrag, предназначенный для NSItemProvider, на View модификатор .draggable(), предназначенный для Transferable объектов:

модификатор draggable()
модификатор draggable()

Заключение

С помощью перечисления enum DropItem, куда мы включили все возможные объекты (текст StringURL-адрес, изображение в виде двоичного файла Data), нам удалось в SwiftUI с помощью нового протокола Transferable обеспечить перетаскивание и "сброс" (Drag & Drop) в один и тот же контейнер (в нашем случае это ZStack, но может быть и любой другой). При реализации протокола Transferable перечисление enum DropItem использует в  static свойстве transferRepresentation ProxyRepresentation для каждого "сбрасываемого" ТИПа.

Код находится на Github.com

Русскоязычный перевод стэнфордских Лекций CS193P по Drag & Drop.

Tags:
Hubs:
Rating0
Comments2

Articles