Как превратить единственный контейнер в 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] …).

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

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

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

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

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

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

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

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

Заключение

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

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

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