In WWDC 2022 and among other interesting announcements, Apple presented a new protocol in Swift, named Transferable. In a nutshell, Transferable makes it really easy and absolutely straightforward to copy data either among different spots within the same app, or even different apps. And when talking about copy, this does not include only copy and paste, but drag and drop as well.
Before Transferable became available, it had been necessary to perform a series of certain steps in order to transfer data as mentioned above. For custom types especially, it had been mandatory to adopt specific protocols and implement some required methods in order to serialize and deserialize, work with item providers and their contained data, and more. However, all related details are not of much interest here, as the topic is the evolution of all that, the Transferable protocol. For someone that has worked with all that, Transferable is definitely a game changer.
With Transferable it takes really little to no effort to copy or drag and drop the following:
- Objects of custom types that conform to Codable protocol
- Data
- Files
Note however that Transferable is not supported by any operating system before its introduction; enabling copy-paste or drag and drop in them still requires to resort to previous programming techniques. In other words, Transferable can be used in iOS 16+, macOS 13+ (Ventura and newer), watchOS 9.0+ and tvOS 16+.
This post focuses on presenting how to drag and drop data with Transferable that originate from a custom type that conforms to Codable protocol. As you will soon find out, the required lines of code related to Transferable are just a few, as it takes care of all the heavy work behind the scenes.
A demo project to work on
In order to explore what it takes in order to enable drag and drop using Transferable, we’ll focus on implementing a small project. On the one side we’ll be displaying a series of a few color item views, and on the other side there will be a small view that will be acting as a drop destination for any of these color items. Nothing fancy, but good enough to talk about Transferable.
Most importantly, each displayed color will be represented programmatically by a custom type that will be conforming to Codable protocol. That has a special weight, as we will do half of the Transferable-related work there.
Before we see all that, here’s a preview of the app we’ll be discussing here:
Credits: Colors from Coolors
Preparing the app
Even though there is a link in the end of this post to download the demonstrated project, here I’m going to provide you with everything you need in order to build it fast from scratch. Once we go through that, we’ll focus on the pure discussion around Transferable.
So, in a new SwiftUI based project which you can name TransferableDemo (make sure to use Xcode 14 as the minimum required version), add a new SwiftUI view source file. You can name it ColorView.swift, as it will be displaying a color item. At the same time, keep the default view (ContentView.swift) intact.
Once it’s ready, open it and paste the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
struct ColorView: View { var colorItem: ColorItem var body: some View { ZStack(alignment: .bottomTrailing) { Color(red: colorItem.red, green: colorItem.green, blue: colorItem.blue) Color.black.opacity(0.3) .frame(height: 50) Text(colorItem.name) .font(.title2) .fontWeight(.bold) .padding() .foregroundColor(.white) } .cornerRadius(8) } } |
This plain view implementation has a ZStack container that displays the color described by the given ColorItem
object, and its name on top of it. The few view modifiers applied style the inner views a bit.
Regarding the ColorItem
type, that’s something that we’ll implement in a while; as its name suggests, it’s the programming representation of a color item.
Note: If you are making this sample app from scratch following the steps here, then you’ll need to replace the ColorView_Previews
struct with the following:
1 2 3 4 5 6 7 |
struct ColorView_Previews: PreviewProvider { static var previews: some View { ColorView(colorItem: ColorItem(id: 0, name: “”, red: 0, green: 0, blue: 0)) } } |
With the ColorView
in place, let’s go to the ContentView.swift file that exists in the project by default. Add the following code there:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
struct ContentView: View { @StateObject private var colors = Colors() @State private var draggedColorItem: ColorItem? @State private var borderColor: Color = .black @State private var borderWidth: CGFloat = 1.0 var body: some View { HStack { VStack { ForEach(colors.items, id: \.id) { colorItem in ColorView(colorItem: colorItem) } } .frame(width: 250) .frame(maxHeight: 750) .padding(.leading) Divider().padding(.horizontal, 75) VStack { if draggedColorItem != nil { ColorView(colorItem: draggedColorItem!) } else { Text(“Drag and Drop a color here!”) .foregroundColor(.secondary) } } .frame(width: 280, height: 220) .background(Color.gray.opacity(0.25)) .border(borderColor, width: borderWidth) .padding(.trailing) } } } |
What this code does in short:
- It lists a few sample colors on the left side of the screen, laying out instances of the
ColorView
view that we met just right above. - In the center there is a divider to visually separate the screen in two parts.
- On the right side there is a VStack that will be acting as the drop area. It displays either a
ColorView
using the dragged color, or a prompt message if no color has been dragged yet. Notice that the border of the VStack gets its values (border color and width) from the respective state properties; we’ll be updating them dynamically while hovering a color item above this view.
The Colors
type presented in the previous code snippet is also another custom type that we’ll implement right next. Consider this as the view model that provides our view with the data it needs to display.
The ColorItem type
The two views are almost ready; the only thing still missing is everything related to Transferable. So, at this point, let’s focus on the ColorItem
type; the model that we made use of already, but it doesn’t exist yet.
As I’ve mentioned, ColorItem
describes programmatically a color item shown on screen. Given that we eventually want to drag and drop objects of this type, it’s necessary to make sure that it conforms to Codable
protocol, so let’s get started with that:
1 2 3 4 5 |
struct ColorItem: Identifiable, Codable { } |
Note: You may create a new Swift source file (ColorItem.swift) in order to add the code described here.
Besides Codable
, ColorItem
also adopts Identifiable
as well, so as to expose the id
property which we use in the ForEach
container in SwitUI part. In its body we’ll have the following properties:
1 2 3 4 5 6 7 8 9 |
struct ColorItem: Identifiable, Codable { var id: Int var name: String var red: Double var green: Double var blue: Double } |
Nothing difficult here; we have the id
property as required by the Identifiable
protocol, a name for the color, and three more properties that store the red, green and blue values of the color.
To help our purpose, let’s return a few sample colors from a static property, and the first round of work on the ColorItem
will be considered ready:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct ColorItem: Identifiable, Codable { … static var sampleColors: [ColorItem] { [ ColorItem(id: 0, name: “Amethyst”, red: 0.6, green: 0.364, blue: 0.898), ColorItem(id: 1, name: “Magenta Crayola”, red: 0.945, green: 0.357, blue: 0.71), ColorItem(id: 2, name: “Minion Yellow”, red: 0.996, green: 0.894, blue: 0.25), ColorItem(id: 3, name: “Capri”, red: 0, green: 0.734, blue: 0.976), ColorItem(id: 4, name: “Sea Green Crayola”, red: 0, green: 0.96, blue: 0.831), ] } } |
Finally, and right before we start working on Transferable, let’s define one more custom type that we made use of already too previously; the Colors
type:
1 2 3 4 5 6 7 8 9 |
class Colors: ObservableObject { @Published var items = [ColorItem]() init() { items.append(contentsOf: ColorItem.sampleColors) } } |
When a Colors
instance is initialized, the sample colors we specified in the last step are appended to the items
array; that’s the datasource for the color items in the ContentView.
Defining a custom content type
When it comes to use Transferable in order to make it possible to either copy or drag and drop an object of a custom type, then there are two distinct steps that we have to make. The first is to tell the system what the type of the object we want to transfer is, or in other words, to specify the Uniform Type Identifier (UTI), also known as content type.
UTI or content type is a way invented by Apple to universally describe various types of data, such as binary files, images, text, audio, video, and a lot more, disregarding other kinds of representations, such as file extensions, MIME types, and other methods which would potentially lead to ambiguities or incompatibilities. Many content types are subtypes of other types; for instance, PNG content type is a subtype of Image type, which in turn is a subtype of Data type.
Apple provides a big number of built-in content types, however we can also define our own, custom UTIs as well. And that’s something we have to necessarily do in situations like this one, where it’s needed to let the system know what kind of data we’re planning to transfer.
The discussion about content types can become quite extensive, but doing so here would take us out of the scope of the tutorial. So, after that short overview about them, what we have to do is to declare a new content type that will be representing a ColorItem
object.
To do that, click on the project name in the Project Navigator and select the TransferableDemo target (or whatever else you named the app). Then open the Info tab. Expand the Exported Type Identifiers section, click on the Plus button, and fill in the following fields with the given values:
- Description: ColorItem
- Identifier: This should be a unique value, and it’s recommended to prefix any value here with the Bundle Identifier of the app. For example, what I’ve set in my project is com.gabrieltheodoropoulos.TransferableDemo.color.
- Conforms To: com.public.data
Leave the rest of the fields empty, as they are not of interest here.
Next, head back to the file where ColorItem
is implemented (or in any other source code file you prefer), and add the following extension:
1 2 3 4 5 |
extension UTType { static var color = UTType(exportedAs: “com.gabrieltheodoropoulos.TransferableDemo.color”) } |
Note: Make sure to set above the value that you also set in the Identifier field in the previous step.
The UTType
represents a UTI programmatically, and here we are extending it in order to declare a static property matching to the custom content type defined right previously. This is not a mandatory part of the process, but it provides great convenience; we’ll be accessing just the color
static property instead of writing the full UTI whenever we need to use it.
Note that it’s necessary to import the following framework so as the UTType
to become available:
1 2 3 |
import UniformTypeIdentifiers |
Making ColorItem conform to Transferable
With the first step being complete and a new custom content type defined, the next move is to make ColorItem
capable of transferring instances of it. What actually happens behind the scenes is that any instance that’s about to be copied or dragged with Transferable, is serialized initially, and then it’s copied into memory. When it’s time to paste or drop it, then it’s deserialized once it’s read from memory, and afterwards it can be used just like the original instance again.
By default, custom types that conform to Codable
are automatically serialized to JSON objects, but it’s also possible to serialize using other representations by providing custom encoder and decoder objects. That’s something, however, that we won’t deal with here.
Back to action again, continue by adopting the Transferable protocol to ColorItem
like so:
1 2 3 4 5 |
struct ColorItem: Identifiable, Codable, Transferable { … } |
Before going any further, make sure to import the SwiftUI framework in order to expose Transferable APIs:
1 2 3 |
import SwiftUI |
Now, there is just one requirement of Transferable to satisfy; to specify at least one transferable item in the following static property:
1 2 3 4 5 6 7 8 9 |
struct ColorItem: Identifiable, Codable, Transferable { static var transferRepresentation: some TransferRepresentation { } … } |
There are specific APIs we can use here. In this particular case where we are dealing with a Codable type, so we’ll specify the CodableRepresentation
as shown next:
1 2 3 4 5 |
static var transferRepresentation: some TransferRepresentation { CodableRepresentation(for: ColorItem.self, contentType: .color) } |
The first argument in the CodableRepresentation
initializer can be omitted, I keep it here however in order to demonstrate how the transferable type can be explicitly specified. In this case, it’s the ColorItem
type.
The contentType
argument though cannot be omitted; it’s required, and this is where we set the content type (UTI) of the custom type that we want to transfer. See that instead of writing the full UTI by initializing a UTType
instance, we simply access the color
static property we had declared in the UTType
extension earlier using the dot syntax, which undeniably is really convenient and handy.
There are two more optional arguments we could also pass to the above initializer. These are the custom encoder and decoder objects, in case we want to serialize using a representation other than JSON.
The above is all we need in order to make it possible to copy-paste or drag and drop objects of the ColorItem
custom type. Transferable handles everything automatically behind the scenes, without requiring any additional actions on our part.
Enabling dragging
Now that the ColorItem
type successfully conforms to Transferable, it’s time to enable drag and drop, so we can move color items around in our app. There is just one place to visit for this purpose, and that is the ColorView view.
To enable dragging, it’s necessary to add one view modifier to the outermost view; the draggable(_:)
:
1 2 3 4 5 6 7 |
ZStack(alignment: .bottomTrailing) { … } // … other view modifiers … .draggable(colorItem) |
The argument we supply it with is the object that we want to drag. Needless to say the obvious, but that object must be of a type that conforms to Transferable protocol. And after our last actions, ColorItem
does so.
Accepting dropping
The drop view for a color item that will be dragged around is the VStack in the ContentView. As we’ve seen in the beginning, this view displays either a color view with the dragged color item, or a prompt text message:
1 2 3 4 5 6 7 8 9 10 11 |
VStack { if let draggedColorItem { ColorView(colorItem: draggedColorItem) } else { Text(“Drag and Drop a color here!”) .foregroundColor(.secondary) } } // … view modifiers … |
In order to allow a dragged color item to be dropped in that VStack, we have to use another view modifier that exists for that purpose. Here it is in its most expanded form:
1 2 3 4 5 6 7 8 9 10 11 |
VStack { … } // … view modifiers … .dropDestination(for: ColorItem.self) { items, location in } isTargeted: { inDropArea in } |
The first argument that specifies the type of the dragged item is optional, and therefore it can be omitted. The isTargeted
argument which is a closure is also optional, but here we have a good opportunity to talk about it. However, the second argument (first closure above) is mandatory.
It has two parameter values:
items
is an array that contains all dragged items, but obviously when dragging one item, this array will be containing one element only.location
reports the position of the dragged item in the drop view, with the top-left corner being the zero point (0, 0). This value could be proved useful sometimes.
Let’s fill in the missing parts now. In the first closure, there’s just one item in the items
array. We’ll assign it to the draggedColorItem
state property, so it’s shown in the VStack view. However, we won’t do anything particular regarding the drop location; we’ll just print it, so we can see what the value of this location is when dragging above the drop area.
Besides implementing any custom logic in the first closure, it’s also necessary to return a Bool value from it; true
if the dragged item is allowed to be dropped, false
otherwise. Not all scenarios will always be that simple as we have here. Sometimes you’ll be filtering the dragged items, and occasionally you’ll need to refuse dropping.
Let’s add the related code for what I just described:
1 2 3 4 5 6 7 8 9 |
.dropDestination(for: ColorItem.self) { items, location in draggedColorItem = items.first print(location) return true } isTargeted: { inDropArea in } |
Finally, there is the last closure that is still empty. This is interesting if only you want to know whether a dragged item is inside or outside the drop area, and you want to perform any visual changes or other actions that depend on that state.
The closure’s parameter value is a Bool value, which when true
, it means that the dragged item is inside the bounds of the drop area. What we’ll do here, is to update the border color and border width of the VStack like so:
1 2 3 4 5 6 7 8 9 |
.dropDestination(for: ColorItem.self) { items, location in … } isTargeted: { inDropArea in print(“In drop area”, inDropArea) borderColor = inDropArea ? .accentColor : .black borderWidth = inDropArea ? 10.0 : 1.0 } |
A thicker border color will be shown every time a color item is dragged inside the drop area region, and will go back to normal when leaving it.
That was the last touch in this sample project that demonstrates how to use the Transferable protocol in order to perform drag and drop easily. If you’ve been following along from the beginning, you can go ahead and give the app a try, and have an actual first taste of the Transferable protocol.
Conclusion
Even though this post has become a bit long because there were quite a few things to explain, as well as to prepare the demo app, it’s a fact that the Transferable related implementation did not require much effort or coding. It’s not an exaggeration that Transferable can be a game changer as I said in the beginning in the way we copy or drag data, as it allows us to do so with the minimum possible hassle. Note that what I presented here is just a part of Transferable; there is more to explore and beyond Codable types, such as transferring data or files. More will come in future posts though. I hope you found this tutorial useful.
Thanks for reading! ????
You can download the sample project from this link.
Related Posts
Transferable Protocol in SwiftUI – Transferring Alternative Content With ProxyRepresentation