The New Transferable Protocol In Swift

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.

The Transferable Protocol

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;

  • String
  • Data
  • URL
  • Attributed String
  • Image
	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;

CodableRepresentation

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

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.

Exporting with DataRepresentation

	extension ImageModel: Transferable {
		static var transferRepresentation: some TransferRepresentation {
        DataRepresentation(exportedContentType: .png) { image in
            try image.pngData()
        }
    }
}

Importing with DataRepresentation

	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.

Exporting with FileRepresentation

	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)
			}
		}
}

Importing with FileRepresentation

	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)
			}
		}
	}

Combining Representations

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)
		}
	}

ProxyPresentation

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.

Subscribe To The Newsletter

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