WWDC Quick Look 💓 By SwiftGGTeam
Meet Transferable

Meet Transferable

元の動画を見る

ハイライト

SwiftUI エンジニアの Julia は、「女性コンピュータ科学者ディレクトリ」アプリを例として、Transferable プロトコルの設計と使用法を紹介しました。

主要内容

Julia のサンプル アプリには、女性コンピューター科学者のプロフィールが保存されています。プロフィール ページには、名前、自己紹介、興味深い事実、ポートレート、ビデオが含まれます。ユーザーは、肖像画をアプリにドラッグしたり、クリップボードから逸話を貼り付けたり、watchOS でプロフィールを共有したりするなど、いくつかの日常的なことを実行したいと考えています。 (00:38)

以前は、これらのエントリはさまざまな API に分散していることがよくありました。ドラッグ アンド ドロップ、コピー アンド ペースト、およびシートの共有はすべて、モデルを受信者が理解できるデータに変換し、データのタイプを宣言します。Transferableこの問題をモデル層に戻します。モデル自体は、エクスポート方法、インポート方法、および対応する種類を宣言します。UTType。(02:09

この話のポイントは、共有ボタンを書くことではありません。ポイントは、ProfileProfilesArchiveVideoこれらのモデルは転送ルールを一度宣言し、その後PasteButton.draggable.dropDestinationShareLinkSwiftUI エントリが再利用されるまで待ちます。 (05:10)

詳細

最初にデータ型を宣言します

(04:18) アプリ間でデータを転送する場合、受信者はバイナリ データが何を表しているのかを知る必要があります。 Apple は、Uniform Type Identifier (UTType) を使用してデータ構造を記述します。カスタム モデルに独自のファイル形式がある場合は、最初にカスタム タイプを宣言する必要があります。

import UniformTypeIdentifiers

// also declare the content type in the Info.plist
extension UTType {
    static var profile: UTType = UTType(exportedAs: "com.example.profile")
}

重要なポイント: -import UniformTypeIdentifiers導入UTType

  • extension UTTypeビジネス セマンティック名をシステム タイプ テーブルに追加します。 -UTType(exportedAs:)このタイプが現在のアプリによって定義され、エクスポートされることを示します。
  • メモのリマインダーはまだ残っていますInfo.plist声明;セッションでは、アプリがこの種のファイルを開けられることをシステムに知らせるために、同時にファイル拡張子を追加することをお勧めします。

標準タイプはすでに直接送信可能です

05:01StringDataURLAttributedStringImageすでにフォローされていますTransferable。機能にこれらの標準タイプのみが含まれる場合は、SwiftUI エントリを直接使用できます。

import SwiftUI

struct Profile {
    private var funFacts: [String] = []
    mutating func addFunFacts(_ newFunFacts: [String]) {
        funFacts.append(newFunFacts)
    }
}

struct PasteButtonView: View {
    @State var profile = Profile()
    var body: some View {
        PasteButton(payloadType: String.self) { funFacts in
            profile.addFunFacts(funFacts)
        }
    }
}

重要なポイント: -funFacts文字列配列を使用して、人物に関する逸話を保存します。 -addFunFacts新しい文字列を受け取り、それをモデルに追加します。 -@State var profileビューに変更可能なデータ モデルを保持させます。 -PasteButton(payloadType: String.self)クリップボード内の文字列コンテンツのみを受信することを宣言します。

  • 閉鎖中funFactsシステムによって解析されたペイロードは、モデルに直接書き戻されます。

同じ標準タイプをドラッグ アンド ドロップにも使用できます。以下のポートレートビューは、Imageドラッグ ソースとドロップ ターゲットの両方として機能します。

import SwiftUI

struct PortraitView: View {
    @State 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
            }
    }
}

重要なポイント: -@State var portrait現在のアバター画像を保存します。 -.draggable(portrait)電流を流してくださいImageドラッグ可能なコンテンツになります。 -.dropDestination(payloadType: Image.self)画像ドロップを受け取るには、このビューを宣言します。 -images.firstドロップされた最初の写真を入手します。

  • 正常に更新されましたportrait後に戻るtrue、画像が利用できない場合に返されますfalse

CodableRepresentation は通常のモデルに適しています

06:34Profileこの分野の中心モデルです。それは持っていますidnamebiofunFactsvideoそしてportrait、フォローしてきましたCodable

import Foundation

struct Profile: Codable {
    var id: UUID
    var name: String
    var bio: String
    var funFacts: [String]
    var video: URL?
    var portrait: URL?
}

重要なポイント: -Codableモデルにエンコード機能とデコード機能を持たせます。 -id使用UUID個々のデータを識別します。 -funFacts貼り付け可能な文字列データを保存します。 -videoそしてportraitオプションを使用するURL外部リソースを指します。

(07:31)Profileすでにエンコードとデコードが可能で、転送可能な適合性を提供するだけで済みますtransferRepresentationCodableRepresentationデフォルトでは JSON が使用されますが、カスタムのエンコーダー/デコーダーも使用できます。

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

重要なポイント: -import CoreTransferable導入Transferableそして表現タイプ。 -extension Profile: Codable, Transferable声明Profileシステム送信に参加できます。 -transferRepresentation従うことですTransferable実装される静的プロパティ。 -CodableRepresentation(contentType: .profile)使用Codableバイナリデータを生成し、コンテンツをカスタムとしてマークします.profileタイプ。 -UTType.profileそしてCodableRepresentationコンテンツ タイプは一貫したままです。

インメモリバイナリ形式に適した DataRepresentation

07:49ProfilesArchiveCSV はすでにサポートされています。 CSVから取得できますData初期化、CSVエクスポートも可能Data。このモデルはこんな方に適しています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)
        }
    }
}

重要なポイント: -init(csvData:)受信した CSV データからアーカイブを再構築する方法について説明します。 -convertToCSV()アーカイブをCSVデータとしてエクスポートする方法について説明します。 -DataRepresentation(contentType: .commaSeparatedText)転送形式をCSVテキストとして宣言します。

  • クロージャのエクスポートProfilesArchiveに変換するData
  • インポートクロージャーは受信したものを受け取りますData転送して戻すProfilesArchive

FileRepresentation は大きなファイルに適しています

(08:46) ビデオはサイズが大きくなる場合があります。 Julia は、ビデオをメモリにロードしたくないことを明確に示しています。FileRepresentationディスク ファイルは送信キャリアとして使用され、受信側はファイル URL を使用してモデルを再構築します。

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

重要なポイント: -Videoファイルを保存するURL、ビデオ コンテンツをモデル属性に含めないでください。 -FileRepresentation(contentType: .mpeg4Movie)これが MPEG-4 動画ファイルであることを宣言します。 -SentTransferredFile($0.file)既存のファイルを転送のためにシステムに送信します。

  • インポートするクロージャは、まず受信したファイルをコピーし、次にコピーされたアドレスを使用してファイルを作成します。Video
  • FileManager.default.url(for:in:appropriateFor:create:)ユーザーの映画ディレクトリを見つけます。
  • ターゲットファイル名がすでに存在する場合、コードは追加します。UUIDカバーは避けてください。 -copyItem(at:to:)ファイルのコピーを完了し、アプリ自体が管理するターゲットアドレスに戻ります。

ProxyRepresentation は劣化した表現を提供します

(10:04) 一部の受信機はカスタムを認識しません.profile入力しますが、テキストは認識されます。ProxyRepresentation現在のモデルを、すでに転送可能な別のタイプで表現できるようにします。ここで使用されますname代表するProfile

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

重要なポイント: -CodableRepresentation(contentType: .profile)最も完全なカスタム データ形式を維持します。 -ProxyRepresentation(exporting: \.name)追加の名前を文字列としてエクスポートします。

  • プレゼンテーションの順序は重要です。受信者は、サポートする最初のコンテンツ タイプを使用します。
  • 独自のアプリを理解する.profileクリックすると完全な情報が表示されます。通常のテキスト ボックスは、テキスト メッセージ送信時の名前の取得のみをサポートします。

(11:20) ビデオにはファイル表現と URL プロキシの両方を含めることもできます。FileRepresentation受信者にディスク ファイルの内容にアクセスできるようにし、一時的なサンドボックス拡張機能を通じてアクセス権を確保します。ProxyRepresentationバンドルURL通常の可搬性値として扱われます。

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

重要なポイント:

  • 1階FileRepresentationディスク上の動画ファイルを転送します。
  • クロージャをインポートしてもファイルはコピーされ、一時的な場所への依存が回避されます。 -ProxyRepresentation(exporting: \.file)ダウングレード出力を URL として提供します。
  • ビデオをテキスト フィールドに貼り付ける場合、受信者は URL を使用できます。受信者がファイルのコンテンツを必要とする場合は、ファイル表現を使用します。

###exportingCondition は条件付きエクスポートを表します

(12:35) 一部のアーカイブは CSV へのエクスポートをサポートしていません。exportingConditionこのビジネス条件を代表者に書き込みます。条件が満たされない場合、この形式はエクスポートされません。

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

重要なポイント: -supportsCSV「このアーカイブを CSV にエクスポートできますか」をモデル プロパティにします。 -DataRepresentationCSV のインポートとエクスポートの方法については引き続き説明します。 -.exportingCondition { $0.supportsCSV }エクスポートする前に現在のアーカイブを確認してください。

  • 条件はfalse、アーカイブは CSV として受信者に提供されません。

重要ポイント

1. データカードにコピー&ペーストを追加する

  • やるべきこと: 連絡先、ポートフォリオ、コースカードを利用できるようにするTransferable、テキスト ボックスにコピーされたときにタイトルまたは名前が出力されます。
  • 実行する価値がある理由:ProxyRepresentation(exporting:)すべての受信者がプライベート形式を理解する必要がなく、カスタム モデルに対してテキストの劣化を提供できます。
  • 開始方法: 最初に使用しますCodableRepresentation(contentType:)完全なモデルを宣言して追加するProxyRepresentation(exporting: \.name)または同様の分野。

2. 画像編集結果をドラッグアンドドロップする

  • 内容: 編集した画像をプレビュー領域から Finder、メール、または別のウィンドウにドラッグできるようにします。
  • 実行する価値がある理由: セッションでは、Image がすでに Transferable に準拠しており、.draggable.dropDestination で標準型をそのまま使えることが示されています。
  • 開始方法: SwiftUI ビューに追加.draggable(image);受信エリア用.dropDestination(payloadType: Image.self)ローカルステータスを更新します。

3. バッチのインポートおよびエクスポート形式として CSV を使用する

  • 作業内容: CSV のインポートとエクスポートを台帳、名簿、在庫テーブルに追加します。
  • 実行する価値がある理由:DataRepresentation例では、既存のメモリ データ形式を持つモデルに適しています。ProfilesArchiveCSVアーカイブです。
  • 開始方法: アーカイブ タイプに実装init(csvData:)そしてconvertToCSV()、存在するDataRepresentation(contentType: .commaSeparatedText)これら 2 つの関数を呼び出します。

4. 大きなビデオはファイルのみを転送します

  • 何をするか: 動画編集、運動記録、授業録画 App で素材を共有するときは、メモリ上のデータ全体ではなくファイルを転送します。
  • 実行する価値がある理由: session では動画が非常に大きくなり得ると明確に述べており、FileRepresentation によるディスク ファイル転送は、一度にメモリへ読み込むことを避けるのに適しています。
  • 開始方法: モデルにファイル URL を保存し、FileRepresentation(contentType: .mpeg4Movie)SentTransferredFile を返し、インポート時は App 管理ディレクトリへコピーします。

5. モデルのステータスに応じてエクスポート可能な形式を制御する

  • 内容: ドキュメントのレンダリングが完了しているか、アーカイブが CSV をサポートしているか、ユーザーが権限を持っている場合にのみ、エクスポート形式を公開します。
  • 実行する価値がある理由:exportingCondition「エクスポートが許可されるかどうか」は、現在のモデル インスタンスにバインドできます。
  • 開始方法: ブール属性をモデルに追加し、表現の後に追加します。.exportingCondition { $0.someFlag }

関連セッション

コメント

GitHub Issues · utterances