Understanding the Transferable Protocol in Swift

Understanding the Transferable Protocol in Swift

Learn how to prepare your custom types to be sharable between applications and system features with the Transferable protocol.

Many app use cases involve moving information from outside the application to inside it, and vice versa. An application that creates or edits images, for example, benefits from being able to receive images from other applications and adapt so it can be used within the functionalities of the app, while the user would be happy to be able to get an image from the app and use it in social media applications, other image editing software or other apps in their workflow.

The Core Transferable framework, which provides an approach to make the types in your application available for sharing and transferring data, defines the Transferable protocol.

Transferable describes different data representations for a type. It is a declarative way to support data transfer functionality in your app, such as drag-and-drop and copy-and-paste, by describing how your models can be serialized or deserialized. The goal is to make the models we already have in our application support being sent to a receiver within our app or to other applications.

By conforming your types with the Transferable protocol you will be able to use them with APIs like PasteButton, ShareLink, and the draggable(_:) and dropDestination(for:isEnabled:action:) modifiers.

Conforming to Transferable

When conforming your type with the Transferable protocol, you need to provide a representation of how the information associated with it should be exported and imported.

This representation is called a transferRepresentation and defines how the type will be transformed into data for transfer and later reconstructed.

struct Note: Codable {
    var id: UUID
    var title: String
    var body: String
    var creationDate: Date
}

extension Note: Transferable {
    static var transferRepresentation: some TransferRepresentation {
        // Representation here
    }
}

There are different ways you can define the representation of your data, and they are covered in the Apple documentation article Choosing a transfer representation for a model type. They are:

The Uniform Type Identifiers framework provides a description of file types for storage or transfer, such as plainText, image and pdf, but keep in mind that when working with CodableRepresentation, you are also defining a custom data type for your app, meaning you should define it in your project configuration as well.

Check the article Defining file and data types for your app to understand how to declare uniform type identifiers to support your app’s proprietary data formats.

In your code, it will look like the following:

import UniformTypeIdentifiers

extension UTType {
    static var post: UTType = UTType(exportedAs: "com.createwithswift.post")
}

To other types to take the responsibility for representing your type, you can use ProxyRepresentation. Types that conform to Transferable, like String and Data, can then be used to represent your type in different contexts. For example, when you copy your type and paste it into a text field, you might want to provide a textual representation, but when you drag and drop it into a context that supports it, you provide a full representation of the data.

extension Post: Transferable {
    static var transferRepresentation: some TransferRepresentation {
        CodableRepresentation(contentType: .post)
        ProxyRepresentation(exporting: \.title)
    }
}

Here is a simple example of the Transferable protocol in use. We are defining a type that can be shared, dragged, and pasted between apps or screens and allowing the system to convert the type to a data representation:

import SwiftUI
import UniformTypeIdentifiers

struct Post: Codable, Transferable {
    var text: String
    
    static var transferRepresentation: some TransferRepresentation {
        DataRepresentation(contentType: .plainText) { post in
            // Post to Data
            post.text.data(using: .utf8) ?? Data()
        } importing: { data in
            // Data to Post
            let content = String(decoding: data, as: UTF8.self)
            return Post(text: content)
        }
    }
}

In here we are using the DataRepresentation(contentType:exporting:importing:) representation.

The first block, exporting, receives the Post object and converts the text parameter’s String value to Data encoded as UTF-8. This tells the system how to convert the custom type Post into transmissible data.

The second block, importing, receives a Data value and converts it to a String value, initializing a new Post object and returning it. This tells the system how to reconstruct the object after it has been transferred.