The Transferable Protocol is a new addition in Swift allowing custom content types to be shared across the Apple operating systems.
Posted By Adam Bulmer In Swift,SwiftUI
With iOS 16, Apple has provided us with a new protocol called Transferable
, enabling data to be shared between different applications, the same application and users.
Transferable
replaces NSItemProvider
introduced in iOS 8. SwiftUI makes use of the Transferable protocol when implementing the new SwiftUI components ShareLink and PasteButton.
When we want to share data between our apps, the data models must conform to the Transferable
Protocol to describe how our models will be serialised and deserialised. It is also how we represent the content type, so the receiving application knows what has just been received.
Many content types within the Apple operating systems already conform to Transferable
and can be used with ShareLink. These are;
ShareLink(item: URL(string: "https://swiftforjs.dev")!) {
Label("Share", systemImage: "square.and.arrow.up")
}
To make a custom data model transferable, we first have to create a custom content type that can be defined as a uniform type identifiers.
import UniformTypeIdentifiers
extension UTType {
static var todo: UTType = UTType(exportedAs: "com.swiftforjs.todo")
}
Once a custom content type has been defined, you can implement the Transferable protocol by extending your existing data models.
To implement the Transferable protocol, you must implement the static transferRepresentation property that tells the system how to import and export the data.
Apple has provided different transfer representations and you can even define your own. System-defined transfer representations will cover most use-cases. They include;
You can use CodableRepresentation
for data models that already implement the Codable protocol.
struct Todo: Codable {
name: String
completed: Boolean
}
extension Todo: Transferable {
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .todo)
}
}
DataRepresentation
and FileRepresentation
are representations of binary data.
DataRepresentation
should be used for small amounts of data that can be kept in memory. E.g. Images.
extension ImageModel: Transferable {
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(exportedContentType: .png) { image in
try image.pngData()
}
}
}
extension WeeklyTrends: Transferable {
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(importedContentType: .commaSeparatedText) { csvData in
try WeeklyTrends(csvData: csvData)
}
}
}
You should opt for the FileRepresentation
when you want to transfer large amounts of data efficiently. The system will pass file urls that your application can lazy load into memory when required.
extension YearlyTrends: Transferable {
let url: URL
static var containerUrl = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents", isDirectory: true)
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(exportedContentType: .commaSeparatedText) { csvFile in
SentTransferredFile(csvFile.url)
}
}
}
extension YearlyTrends: Transferable {
let url: URL
static var containerUrl = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents", isDirectory: true)
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(importedContentType: .commaSeparatedText) { received in
let path = container.appendingPathComponent("\(file.lastPathComponent)-copy")
let copy: URL = URL(fileURLWithPath: path)
try FileManager.default.copyItem(at: received.file, to: copy)
return Self.init(url: copy)
}
}
}
So far, I have only shown examples where a single representation is provided. It is possible in iOS 16 to provide multiple representations for our data.
Note the ordering of representations is important. If the receiver is aware of our custom content type, they will use the first representation and cascade down the list of representations.
struct Todo: Codable {
name: String
completed: Boolean
}
extension Todo: Transferable {
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .todo)
ProxyRepresentation(exporting: \.name)
}
}
In the example above, I made use of ProxyRepresentation
.
A transfer representation that uses another type’s transfer representation as its own.
The transfer representation is helpful for cases where the receiver is unaware of our custom content type and requires a fallback.
By using the ProxyRepresentation
, you can support a custom content type to be pasted into a TextField by selecting an individual property from the model.
If you want to create your first native app or have just begun your native app development journey, be sure to sign up to the newsletter. No Spam