2022 EssentialsSwiftUI & UI Frameworks
WWDC22 · 14 min · Essentials / SwiftUI & UI Frameworks
Meet Transferable
Meet Transferable: a model-layer protocol that allows for effortless support for sharing, drag and drop, copy/paste, and other features in your app. We’ll explore how you can use the API for common use cases, and take advantage of advanced features to customize the behavior. We’ll also share how you can optimize for memory efficiency when dealing with large amounts of data. Whether you’re extending your models to share with other applications as strings or images or creating custom declared data types, Transferable can help you facilitate a great experience in your app.
Watch at developer.apple.com ↗Code shown on screen · 11 snippets
Declaring a custom content type
import UniformTypeIdentifiers
// also declare the content type in the Info.plist
extension UTType {
static var profile: UTType = UTType(exportedAs: "com.example.profile")
} PasteButton interface
import SwiftUI
struct Profile {
private var funFacts: [String] = []
mutating func addFunFacts(_ newFunFacts: [String]) {
funFacts.append(newFunFacts)
}
}
struct PasteButtonView: View {
var profile = Profile()
var body: some View {
PasteButton(payloadType: String.self) { funFacts in
profile.addFunFacts(funFacts)
}
}
} Drag and Drop
import SwiftUI
struct PortraitView: View {
var portrait: Image
var body: some View {
portrait
.cornerRadius(8)
.draggable(portrait)
.dropDestination(payloadType: Image.self) { (images: [Image], _) in
if let image = images.first {
portrait = image
return true
}
return false
}
}
} Sharing
import SwiftUI
struct Profile {
var name: String
}
struct ProfileView: View {
private var portrait: Image
var model: Profile
var body: some View {
VStack {
portrait
Text(model.name)
}
.toolbar {
ShareLink(item: portrait, preview: SharePreview(model.name))
}
}
} Profile structure
import Foundation
struct Profile: Codable {
var id: UUID
var name: String
var bio: String
var funFacts: [String]
var video: URL?
var portrait: URL?
} CodableRepresentation
import CoreTransferable
import UniformTypeIdentifiers
struct Profile: Codable {
var id: UUID
var name: String
var bio: String
var funFacts: [String]
var video: URL?
var portrait: URL?
}
extension Profile: Codable, Transferable {
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .profile)
}
}
// also declare the content type in the Info.plist
extension UTType {
static var profile: UTType = UTType(exportedAs: "com.example.profile")
} DataRepresentation
import CoreTransferable
import UniformTypeIdentifiers
struct ProfilesArchive {
init(csvData: Data) throws { }
func convertToCSV() throws -> Data { Data() }
}
extension ProfilesArchive: Transferable {
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(contentType: .commaSeparatedText) { archive in
try archive.convertToCSV()
} importing: { data in
try ProfilesArchive(csvData: data)
}
}
} FileRepresentation
import CoreTransferable
struct Video: Transferable {
let file: URL
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(contentType: .mpeg4Movie) {
SentTransferredFile($0.file)
} importing: { received in
let destination = try Self.copyVideoFile(source: received.file)
return Self.init(file: destination)
}
}
static func copyVideoFile(source: URL) throws -> URL {
let moviesDirectory = try FileManager.default.url(
for: .moviesDirectory, in: .userDomainMask,
appropriateFor: nil, create: true
)
var destination = moviesDirectory.appendingPathComponent(
source.lastPathComponent, isDirectory: false)
if FileManager.default.fileExists(atPath: destination.path) {
let pathExtension = destination.pathExtension
var fileName = destination.deletingPathExtension().lastPathComponent
fileName += "_\(UUID().uuidString)"
destination = destination
.deletingLastPathComponent()
.appendingPathComponent(fileName)
.appendingPathExtension(pathExtension)
}
try FileManager.default.copyItem(at: source, to: destination)
return destination
}
} ProxyRepresentation
import CoreTransferable
import UniformTypeIdentifiers
struct Profile: Codable {
var id: UUID
var name: String
var bio: String
var funFacts: [String]
var video: URL?
var portrait: URL?
}
extension Profile: Transferable {
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .profile)
ProxyRepresentation(exporting: \.name)
}
}
// also declare the content type in the Info.plist
extension UTType {
static var profile: UTType = UTType(exportedAs: "com.example.profile")
} Proxy and file representations
import CoreTransferable
struct Video: Transferable {
let file: URL
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(contentType: .mpeg4Movie) { SentTransferredFile($0.file) }
importing: { received in
let copy = try Self.copyVideoFile(source: received.file)
return Self.init(file: copy) }
ProxyRepresentation(exporting: \.file)
}
static func copyVideoFile(source: URL) throws -> URL {
let moviesDirectory = try FileManager.default.url(
for: .moviesDirectory, in: .userDomainMask,
appropriateFor: nil, create: true
)
var destination = moviesDirectory.appendingPathComponent(
source.lastPathComponent, isDirectory: false)
if FileManager.default.fileExists(atPath: destination.path) {
let pathExtension = destination.pathExtension
var fileName = destination.deletingPathExtension().lastPathComponent
fileName += "_\(UUID().uuidString)"
destination = destination
.deletingLastPathComponent()
.appendingPathComponent(fileName)
.appendingPathExtension(pathExtension)
}
try FileManager.default.copyItem(at: source, to: destination)
return destination
}
} Exporting condition
import CoreTransferable
import UniformTypeIdentifiers
struct ProfilesArchive {
var supportsCSV: Bool { true }
init(csvData: Data) throws { }
func convertToCSV() throws -> Data { Data() }
}
extension ProfilesArchive: Transferable {
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(contentType: .commaSeparatedText) { archive in
try archive.convertToCSV()
} importing: { data in
try Self(csvData: data)
}
.exportingCondition { $0.supportsCSV }
}
} Resources
Related sessions
-
34 min -
28 min -
24 min -
24 min