View in English

  • メニューを開く メニューを閉じる
  • Apple Developer
検索
検索を終了
  • Apple Developer
  • ニュース
  • 見つける
  • デザイン
  • 開発
  • 配信
  • サポート
  • アカウント
次の内容に検索結果を絞り込む

クイックリンク

5 クイックリンク

ビデオ

メニューを開く メニューを閉じる
  • コレクション
  • トピック
  • すべてのビデオ
  • 利用方法

WWDC22に戻る

ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。

  • 概要
  • トランスクリプト
  • コード
  • Transferableの紹介

    Transferableは、ご利用のAppでの共有、ドラッグアンドドロップ、コピー&ペーストなどの機能を簡単にサポートできるモデルレイヤープロトコルです。 一般的なユースケースにおけるAPIの使用方法や、高度な機能を活用した動作のカスタマイズ方法について解説します。また、大量のデータを処理する場合に、メモリ効率を最適化する方法も紹介します。Transferableは、モデルを拡張して文字列や画像として他のAppと共有する場合でも、カスタム宣言されたデータタイプを作成する場合でも、ご利用のAppに優れたエクスペリエンスをもたらしてくれます。

    リソース

    • ShareLink
      • HDビデオ
      • SDビデオ

    関連ビデオ

    WWDC22

    • カスタムコラボレーションAppを メッセージAppと統合する
    • メッセージAppで共同制作の体験を強化する
    • Apple Watch用の仕事効率化Appの構築
    • SwiftUIの最新情報

    Tech Talks

    • Uniform Type Identifierの再導入
  • このビデオを検索

    ♪メロウなインストゥルメンタル ヒップホップ音楽♪ ♪ こんにちは Transferableセッションへようこそ SwiftUIエンジニアのJuliaです Transferableを紹介でき興奮しています ドラッグ&ドロップ コピー&ペースト その他のAppの機能をサポートする 宣言的方法について説明します SwiftUIやMacのApp開発とは別に コンピュータサイエンスにおける 女性技術者に興味がありました ヒーローを知ることは重要ですよね 女性技術者を表示追加編集できる カタログAppを作りました エンジニアや科学者のプロフィールです このAppはApp間でシームレスに 科学者のプロフィールや知識の 移動やドラッグ&ドロップを サポートすることを目指します また初めてAppで watchOSとの共有をサポートします ウォッチから パーソナリティ・プロフィールを 共有したいとの要望がありました SwiftUIによってiOSとMacでも共有できます 今年ShareSheetのデザインも一新されました すべてを可能にするには App内や 他のアプリケーションの レシーバーへの送信をサポートする モデルが必要です プロフィールのstructには 一人の人格に関する すべての情報が含まれています すべてのプロフィールをアーカイブして 友達と共有できます 技術者の情報を文字で保存し ビデオも添付できます これらのモデル・タイプをすべて共有する 新しい容易な方法があります Transferableを紹介します! これは共有やデータ転送のために モデルをシリアライズ およびデシリアライズする方法を 記述するためのSwift初の宣言的な方法です 今日は Transferableとは何か それを使うときに裏で何が起こっているのか カスタムタイプへの適合方法 最後にTransferableをカスタマイズして 必要なことを行うのに役立つ 高度なヒントやトリックについてお話しします スタートしましょう! 2つのApp が起動しており ユーザーがコピー&ペースト ShareSheet・ドラッグ または他のAppの機能を使って あるAppから別のAppに情報を渡したい場合 2つの異なるApp間で何かを送信するとき このすべてのバイナリデータが行き交います このデータを送信する際に重要なのは そのデータが何に対応しているかを 判断することです テキストやビデオや ある女性技術者のプロフィール あるいは全体のアーカイブかもしれません データが何なのか説明するUTTypeもあります Appのバイナリデータの生成を見てみましょう 他のAppあるいは同じApp内で 共有される情報をバイナリデータに あるいはその逆に 相互変換する方法と バイナリデータの構造を表す コンテントタイプが必要です コンテントのタイプ-- uniform type identifiers とは 異なるバイナリーの構造と抽象概念の 識別子について記述する アップルの独自技術です 識別子はツリーで カスタム識別子も定義できます 例えばプロフィールのバイナリデータ構造 カスタム識別子の宣言に使います 宣言をInfo.plistファイルに加え ファイル拡張子も 加えます データがディスク上にある場合 システムはこれをあなたのAppが開ける事を この情報から知ります 次にコードで宣言します コンテンツのタイプの学習には このビデオをすすめます “Uniform Type Identifierの再導入” Uniform Type Identifiersとは何で どう使うのか 明快に説明しています 良いニュースは多くの標準タイプが すでにTransferableに準拠しています 例えば 文字列・データ・URL 書式付き文字列・画像 などです 新しいSwiftUIのペーストボタンを使う 数行のコードで プロフィールにペーストしたり ビューから画像をドラッグしたり Finderや他のAppからの 画像のドロップをサポートします 新らしいShareLinkで ウォッチから共有を実装できます Transferableとは何かと その使い方の基本を説明しました Appのモデルに Transferable 準拠を 追加する方法を見てみましょう 言及したように今回のAppでは 4つのモデルタイプを共有します 文字列は既に対応しています 何も必要 ありません 1つのプロフィール プロファイルのアーカイブ およびビデオの共有ではどうでしょう Transferableに準拠させるために 実装するのはただ一つ TransferRepresentation だけです モデルがどのように転送されるか記述します 3つ重要なRepresentationがあります CodableRepresentation DataRepresentation FileRepresentation それぞれ説明します 最初は中心となるモデルのProfileの構造です id・名前・略歴・趣味と ポートレイトかビデオです 既にCodableに準拠しますので TransferableにはCodableRepresentationを 含めることができます Codableは エンコーダで プロフィールをバイナリデータへ変換し デコーダが解読します デフォルトでJSONを使用しますが 自分のエンコーダとデコーダも使用できます Codableプロトコルの詳細と エンコーダとデコーダ仕組みは このプロトコルが最初に登場したWWDC18の 「データを信頼できるものに」をご覧ください プロフィールに戻ると Codableで必要な情報は 希望のコンテンツのタイプのみです カスタム・フォーマットになるので カスタムと宣言された uniform type identifierを使います プロフィールとコンテンツタイプ を加え完了です プロフィールは Transferableに 準拠しています! 別のケースのProfilesArchiveです 既にCSVデータ変換が サポートされていますので リストを CSVファイルでエクスポーし 友達とのシェアしたり 別のマシンにインポートできます これでArchiveはDataに相互変換できますので DataRepresentationに準拠したことになります 中を覗いてみると DataRepresentationは変換関数を使って 直接バイナリ表現を作り レシーバーのために値を再構築しています DataRepresentationの使用で Transferable対応は容易です 2つの既にある関数 initializer またCSVコンバータを呼び出し 利用するだけです プロフィールにビデオがある場合 ドラッグと共有もしたいですね しかしビデオは大きくなります メモリーにはロードしたくないですね そこでFileRepresentationです 再び中身を見てみると FileRepresentationは提供されるURLを レシーバーに送信しそれを使って Transferable アイテムを再構築しています FileRepresentation は ディスク・ファイルに書かれた バイナリー表現で共有を可能にします 要約しますと 単純なユースケースで 単一の表現だけの場合 まずそのモデルがCodableに準拠していて 特定のバイナリ形式が必要か確認します 必要であれば CodableRepresentation を使用します そうでなければメモリーかディスクに 保存可能かチェックします メモリーならDataRepresentation ディスクならFileRepresetnationです Transferable はシンプルな使用だけでなく 複雑なものにも対応し ほとんどの場合わずか数行です 見てください! ProfileはTransferableに 準拠していますが さらにProfileがコピーされ テキスト欄にペーストされる場合 プロフィールの名前を貼りたいですよね もう一つの表現を加える必要があります ProxyRepresentationは他のTransferableで 我々のモデルを表現することを可能にします Profileは1行のテキストで貼りつけられます ProxyRepresentationは Codableの後に追加しました 順番は重要です レシーバーは対応するコンテンツタイプの 最初の表現を使います レシーバーがカスタムコンテンツの プロフィールタイプを知っている場合は これを使用します テキストをサポートしていない場合は 代わりにProxyRepresentationを使用します これでProfileは エンコーダとデコーダ両方の変換と テキスト変換をサポートします ProxyRepresentation はこの場合 テキストへのエクスポートのみを記述し プロフィールを再構築することはしません どんな表現も 両方の変換か あるいは1つだけを記述できます ProxyRepresentationsをご説明しましたが ビデオにFileRepresentationは 必要でしょうか? URLでプロキシを利用できます 違いはわずかです FileRepresentationは ディスクに書かれたURLを意図しており 一時的なサンドボックス拡張を付与することで このファイルあるいはそのコピーへの レシーバーのアクセスを保証します ProxyRepresentationはURLを 同じように扱います ファイルに必要な 追加機能はありません つまり両方を持つことができます 最初はFileRepresentationで レシーバーがこのコンテンツの 映画のファイルにアクセスできるようにします 2 番目のものはコピーしたビデオを テキストフィールドにペーストすると 動作します つまりファイルとプロキシの表現で URLの扱いが全く異なるのです 最初のケースでは ペイロードはディスクのアセットです 二番目のケースのペイロードは リモートのWebのURL structです もう一つバージョンアップしたいのが ProfileArchiveのモデルです CSVへの変換に対応していない場合があるので コードに反映させたいと思います 見てみましょう CSVにエクスポートできるか データ間で変換機能があるか Booleanのプロパティを加えます .exportingConditionを使います CSVをサポートしないアーカイブでは このフォーマットでエクスポートできません このAPIを使えば SwiftUIのカスタムViewのように カスタムのTransferRepresentationを 構築することが可能です 唯一の要件は 他の表現を必要な方法で構成することができる bodyプロパティを提供することです 複数の表現を組み合わせて 再利用したい場合や 公に公開したくないプライベートな データ表現がある場合などに便利です Transferableのおかげで 私が望んでいた機能を すべて備えたこのAppを 迅速に構築することができました ありがとうございました 素晴らしいAppを作っていきましょう!

    • 4:36 - 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")
      }
    • 5:10 - PasteButton interface

      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)
              }
          }
      }
    • 5:19 - Drag and Drop

      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
                  }
          }
      }
    • 5:27 - Sharing

      import SwiftUI
      
      struct Profile {
          var name: String
      }
      
      struct ProfileView: View {
          @State private var portrait: Image
          var model: Profile
      
          var body: some View {
              VStack {
                  portrait
                  Text(model.name)
              }
              .toolbar {
                  ShareLink(item: portrait, preview: SharePreview(model.name))
              }
          }
      }
    • 6:34 - 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?
      }
    • 7:31 - 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")
      }
    • 8:30 - 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)
              }
          }
      }
    • 9:14 - 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
          }
      }
    • 10:05 - 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")
      }
    • 11:34 - 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
          }
      }
    • 12:57 - 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 }
          }
      }

Developer Footer

  • ビデオ
  • WWDC22
  • Transferableの紹介
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • App Extension
    • App Store
    • オーディオとビデオ(英語)
    • 拡張現実
    • デザイン
    • 配信
    • 教育
    • フォント(英語)
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内課金
    • ローカリゼーション
    • マップと位置情報
    • 機械学習
    • オープンソース(英語)
    • セキュリティ
    • SafariとWeb(英語)
    メニューを開く メニューを閉じる
    • 英語ドキュメント(完全版)
    • 日本語ドキュメント(一部トピック)
    • チュートリアル
    • ダウンロード(英語)
    • フォーラム(英語)
    • ビデオ
    Open Menu Close Menu
    • サポートドキュメント
    • お問い合わせ
    • バグ報告
    • システム状況(英語)
    メニューを開く メニューを閉じる
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles(英語)
    • フィードバックアシスタント
    メニューを開く メニューを閉じる
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(英語)
    • News Partner Program(英語)
    • Video Partner Program(英語)
    • セキュリティ報奨金プログラム(英語)
    • Security Research Device Program(英語)
    Open Menu Close Menu
    • Appleに相談
    • Apple Developer Center
    • App Store Awards(英語)
    • Apple Design Awards
    • Apple Developer Academy(英語)
    • WWDC
    Apple Developerアプリを入手する
    Copyright © 2025 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン