Transferable Protocol in SwiftUI – Transferring Alternative Content With ProxyRepresentation

Posted in SwiftUI

Updated on August 18th, 2022

⏱ Reading Time: 7 mins

In my previous post we’ve had a first hands-on experience with the Transferable protocol; a new API that was introduced in WWDC 2022, and is meant to reduce dramatically the required efforts on our part in order to copy-paste, or drag and drop data within the same, or different applications.

Becoming more particular, in that first post I demonstrated how to drag and drop objects of custom types that conform to Codable protocol, with several interesting concepts to be covered along the way:

  • content types (UTIs) and how to declare custom ones,
  • proper conformance to Transferable, so objects of the presented custom type to be capable of being dragged,
  • how to start a drag operation in SwiftUI,
  • how to handle dropping in SwiftUI.

This post is pretty much a follow-up on the last one, as it focuses on another feature of Transferable; how to specify additional content to transfer, on top of the primary transferable content representation.

I strongly recommend to read the previous post about Transferable if you have not done so. Not only there are important concepts to understand there, but we will also continue building on the demo project of that post, which you can download from this link.

Before getting into the actual point, let me summarize what the demo app is all about. With a collection of color items being presented on the one side of the screen, the original purpose was to drag each item and drop it to the other side onto a VStack container; to display a duplicate of any color item into the target view by drag and dropping.

In this post we are going to extend that by adding a TextField view as an additional drop area. Ultimately, we’ll make it possible to drag a color item to the TextField, and drop the color’s name on it; a String value only, and not the entire color item object.

Here is a demonstration of all that:

The ProxyRepresentation

As it was made clear in the previous post, any custom type that conforms to Transferable protocol must be mandatorily implementing the following static property:

That’s the place to specify what the transferable item is going to be. In the particular case that is being demonstrated in this and the last post, we have a custom type that describes color items programmatically and conforms to Codable protocol. Therefore, we need to make explicit that objects of this type are going to be the transferable items. Here is how we did that last time in the sample ColorItem custom type:

With the first argument above we explicitly specify the type of the objects that we are planning to transfer. And although this is an optional value that we can avoid to supply, the next one is required; it’s the content type, or in other words, the Uniform Type Identifier (UTI) of the data that will be transferred. In the above example we have a custom content type, and most of the times that you’ll be implementing Transferable in custom types you’ll also need to define your own UTIs as well. Have a look to the previous post to find out more about all that.

With the above, an entire color item can be dragged from one place to another, with the actual instance to be serialized when dragging starts and deserialized on drop. Now, we are going to add something new to that; we are going to make it possible to also transfer just the name of a color item and not the entire object, or to rephrase that, we will specify an additional transferable representation for the color’s name only:

A few important observations:

  • The provided argument above is the key path to the name property, the value of which we want to transfer.
  • The ProxyRepresentaton(exporting:) must be always called after the primary representation. For instance, it would be wrong to call ProxyRepresentation(exporting:) before CodableRepresentation(for:contentType:).
  • The proxy representation actually uses the main representation of another type as if it was its own. Here, that other type is the String, because the name property is of that type. Notice that in contrast to the CodableRepresentation(for:contentType:) we don’t have to specify a content type here. It’s taken from the String type, which is plain text.

That one single line is all we need in order to make the ColorItem type capable of transferring a color item’s name as well. The next step is to add a TextField to the SwiftUI view, and enable dropping there so we can actually receive that name when an item is dragged.

Adding a TextField to the view

In the following code segment you can see the SwiftUI view implementation, taken directly from the original project implemented in the previous post:

The main container view is an HStack so visual elements to be laid out horizontally on screen. On the left side, and in a ForEach container, all sample color items are listed one after the other. On the right side there is a VStack that constitutes the drop destination of a dragged color item. That VStack contains either a dropped color item, or a prompt text message if an item has not been dropped yet.

We want to add a TextField, so we are going to modify the above view. Leaving everything as it currently is up until the divider, we are going to embed the VStack into another VStack like so:

We can now implement the TextField right where the “Add the TextField here..” comment is:

There are a few view modifiers that specify the frame, border and padding of the TextField. Notice that the binding value of the property called colorName is passed as argument to the TextField, but this property currently does not exist; we have to declare it in the view struct before going any further:

The preparation of the SwiftUI view is now complete, so let’s allow dropping on this brand new TextField.

Setting the TextField as a drop destination for the color name

In order to make the text field capable of accepting dropping, it’s necessary to use a particular view modifier; a view modifier that we already have met in the previous post and you can see in the original SwiftUI view implementation listed at the beginning of the last part. That is the dropDestination(for:action:isTargeted:) modifier.

Going through the provided arguments:

  • The first argument is the content type of the transferable item. It’s optional and we can omit it.
  • The second argument is a closure with two parameter values; an array with transferred items, and the location of the finger in iOS or the mouse in macOS within the drop area. We have to return true or false from this closure, depending on whether we accept the drop or not. This argument is required.
  • The last argument is another closure, which is optional as well. Its parameter value informs us whether a dragged item is inside or outside the drop destination; an information that can be proved quite useful often.

Here, and for the sake of demonstration, we are going to provide all arguments. Besides, we are going to need the last one in order to clear the previous color name when another one gets inside the area of the text field.

See the first argument above, where we specify the String type as the expected one for the dragged items in the text field. Next, let’s add the missing content in the isTargeted closure:

When a color item will be dragged above the text field, the inDropArea above will become true. In such cases, we want to empty the value of the colorName property, so the new color name to be set in the first closure.

Speaking of that, let’s add the last piece of the puzzle:

Notice here that the colorName gets either the value of the first item, which is going to be the dragged color item’s name, or an empty string value if no items exist in the items parameter value. The return value of the closure also depends on the existence of elements in the items array; if the first property is not nil, then it’s returned true, otherwise false. Lastly, we don’t care about the location of the dragged item in this particular case, so we simply ignore it.

With this few lines of code only, we managed to make the TextField a drop destination for the name of dragged color items:

Conclusion

It is an undeniable fact that the actual required implementation we made here is far from calling it long, even though the post became a bit lengthy because each step had to be explained along the way. Stepping on the project built in the previous, first tutorial about the Transferable protocol, we just met what it takes in order to specify an additional representation type, what to watch out for, and how to eventually make it possible to transfer alternative content on top of the original one. I hope you found this post useful, and if so, please don’t hesitate to share!

Thanks for reading, take care! ????

Download the final project from this link.

Related Posts

First Experience With Transferable Implementing Drag And Drop In SwiftUI

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.