---
title: ファイル共有の実装方法と落とし穴
tags: 
author: [Kazuki Akiya](https://www.docswell.com/user/Tengu712)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/L739QVQ475.jpg?width=480
description: pixiv App Talk (2026年10月29日)での発表資料。iOSにおけるデータ共有の実装方法とtipsを紹介する。
published: October 29, 26
canonical: https://www.docswell.com/s/Tengu712/58N31X-2026-10-29-140816
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/L739QVQ475.jpg)

ファイル共有の実装方法と落とし穴
pixiv Inc.
Tengu712


# Page. 2

![Page Image](https://bcdn.docswell.com/page/87DG98957G.jpg)

データ共有の実装方法と tips
pixiv Inc.
Tengu712


# Page. 3

![Page Image](https://bcdn.docswell.com/page/VJP3Z8ZDE8.jpg)

自己紹介
• Tengu712
• 2026 年 4 月新卒入社
• お絵描きツール Pastela の開発に従事
• 少々 iOS
• 少々 Web フロント
• 最近インフラ
• Rustacean Vimmer


# Page. 4

![Page Image](https://bcdn.docswell.com/page/2EV4WNWGJQ.jpg)

データ共有
• アプリ間のデータの受渡し
• Share
• Drag &amp; Drop
• Open With (iOS 26+)
• AirDrop
• etc…
• 意外と需要がないので文献が少ない


# Page. 5

![Page Image](https://bcdn.docswell.com/page/57G1DKGDEL.jpg)

データ共有
• アプリ間のデータの受渡し
• Share
• Drag &amp; Drop
• Open With (iOS 26+)
• AirDrop
• etc…
• 意外と需要がないので文献が少ない


# Page. 6

![Page Image](https://bcdn.docswell.com/page/4EQD1NXXJP.jpg)

ユースケース
• URL を受け取ってリストに追加
• PDF を受け取って解析・記録
• 動画を受け取って加工・投稿
• etc…


# Page. 7

![Page Image](https://bcdn.docswell.com/page/KJ4ZRG6271.jpg)

話すこと
• 実装方法
• 単に API を叩くだけではなく、案外考慮すべきことがある


# Page. 8

![Page Image](https://bcdn.docswell.com/page/LE1RQDVKEG.jpg)

実装 (共有元)
• ファイル URL を Share Sheet に渡す
• SwiftUI: ShareLink view
• UIKit: UIActivityViewController
let fileURL = URL.documentsDirectory.appending(
path: &quot;FILE_PATH&quot;,
directoryHint: .notDirectory
)
// SwiftUI
ShareLink(item: fileURL) {
Label(&quot;share&quot;, systemImage: &quot;...&quot;)
}
// UIKit
let sheet = UIActivityViewController(
activityItems: [fileURL],
applicationActivities: nil
)
present(sheet, animated: true)


# Page. 9

![Page Image](https://bcdn.docswell.com/page/GEW1MYLP72.jpg)

実装 (共有先)
• Info.plist に追加
• Drag &amp; Drop が可能に
• Share Sheet にアプリアイコンが出現
&lt;key&gt;CFBundleDocumentTypes&lt;/key&gt;
&lt;array&gt;
&lt;dict&gt;
&lt;key&gt;CFBundleTypeName&lt;/key&gt;
&lt;string&gt;任意の名前&lt;/string&gt;
&lt;key&gt;LSHandlerRank&lt;/key&gt;
&lt;string&gt;形式に対するアプリの立場&lt;/string&gt;
&lt;key&gt;CFBundleTypeRole&lt;/key&gt;
&lt;string&gt;形式に対するアプリの役割&lt;/string&gt;
&lt;key&gt;LSItemContentTypes&lt;/key&gt;
&lt;array&gt;
&lt;string&gt;形式&lt;/string&gt;
...
&lt;/array&gt;
&lt;/dict&gt;
...
&lt;/array&gt;


# Page. 10

![Page Image](https://bcdn.docswell.com/page/47ZPQXM6E3.jpg)

実装例 (共有先)
&lt;key&gt;CFBundleDocumentTypes&lt;/key&gt;
&lt;array&gt;
&lt;dict&gt;
&lt;key&gt;CFBundleTypeName&lt;/key&gt;
&lt;string&gt;Image File&lt;/string&gt;
&lt;key&gt;LSHandlerRank&lt;/key&gt;
&lt;string&gt;Default&lt;/string&gt;
&lt;key&gt;CFBundleTypeRole&lt;/key&gt;
&lt;string&gt;Viewer&lt;/string&gt;
&lt;key&gt;LSItemContentTypes&lt;/key&gt;
&lt;array&gt;
&lt;string&gt;public.png&lt;/string&gt;
&lt;string&gt;public.jpeg&lt;/string&gt;
&lt;/array&gt;
&lt;/dict&gt;
&lt;!-- com.example.myfileは
UTExportedTypeDeclarationsで
定義されているものとする --&gt;
&lt;dict&gt;
&lt;key&gt;CFBundleTypeName&lt;/key&gt;
&lt;string&gt;My File&lt;/string&gt;
&lt;key&gt;LSHandlerRank&lt;/key&gt;
&lt;string&gt;Owner&lt;/string&gt;
&lt;key&gt;CFBundleTypeRole&lt;/key&gt;
&lt;string&gt;Editor&lt;/string&gt;
&lt;key&gt;LSItemContentTypes&lt;/key&gt;
&lt;array&gt;
&lt;string&gt;com.example.myfile&lt;/string&gt;
&lt;/array&gt;
&lt;/dict&gt;
&lt;/array&gt;


# Page. 11

![Page Image](https://bcdn.docswell.com/page/YJ6M64NL7V.jpg)

実装 (共有先)
// SwiftUI
@main struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
// ...
}
}
}
}
// UIKit
class SceneDelegate: UIWindowSceneDelegate {
func scene( // 起動時
_ scene: UIScene,
willConnectTo session: UISceneSession,
options options: UIScene.ConnectionOptions
) {
let urls = options.urlContexts.map(\.url)
// ...
}
func scene( // 復帰時
_ scene: UIScene,
openURLContexts contexts: Set&lt;UIOpenURLContext&gt;
) {
let urls = contexts.map(\.url)
// ...
}
}


# Page. 12

![Page Image](https://bcdn.docswell.com/page/GJ5Z6Q5ME4.jpg)

実装例 (共有先)
@main struct MyApp: App {
@State private var image: UIImage?
var body: some Scene {
WindowGroup {
Group {
if let image {
Image(uiImage: image)
.resizable()
.scaledToFit()
} else {
EmptyView()
}
}
.onOpenURL { url in
image = UIImage(contentsOfFile: url.path)
}
}
}
}


# Page. 13

![Page Image](https://bcdn.docswell.com/page/9E2RMPNRJR.jpg)



# Page. 14

![Page Image](https://bcdn.docswell.com/page/D7YDX5P57M.jpg)

Open in Place
• 共有元のファイルを直接参照する設定
• 共有元が in place に共有している
• かつ共有先アプリが対応している
• Files の Open With が利用可能に
// 共有元
let provider = NSItemProvider()
provider.registerFileRepresentation(
for: .png,
visibility: .all,
openInPlace: true // ← これ
) { completion in
completion(url, true, nil)
return nil
}
// 共有先
// project.pbxproj
// Xcode上ではSupports Documents in Place
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace
= YES;


# Page. 15

![Page Image](https://bcdn.docswell.com/page/VEN61N54E8.jpg)



# Page. 16

![Page Image](https://bcdn.docswell.com/page/Y79L5RY4J3.jpg)

Security Scope
• 権限問題
let isSecurityScoped =
url.startAccessingSecurityScopedResource()
• シミュレータでは問題なく動作
• 実機では権限エラー
• Security scope 付きで開く必要
defer {
if isSecurityScoped {
url.stopAccessingSecurityScopedResource()
}
}


# Page. 17

![Page Image](https://bcdn.docswell.com/page/G78XYW6VED.jpg)

スクリーンショット
• スクリーンショットの共有は
public.image である UIImage の keyed
archive
• 非ファイル
• CFBundleDocumentTypes はファイル専用
• public.image を LSItemContentTypes
に追加しても受け入れられない


# Page. 18

![Page Image](https://bcdn.docswell.com/page/L7L84N9RJR.jpg)

Share Extension
• データ共有のための機能
• 共有時に view を表示できる
• 共有元アプリを維持しつつ共有できる
• 共有先アプリの自動起動は非推奨


# Page. 19

![Page Image](https://bcdn.docswell.com/page/4EM6KXZP7W.jpg)

実装
1. Share Extension ターゲットを追加
2. NSExtensionActivationRule を設定
3. App Groups を設定
4. ShareViewController で共有データ保存
5. アプリから読み出して利用


# Page. 20

![Page Image](https://bcdn.docswell.com/page/PERPXN6Y79.jpg)

実装
1. Share Extension ターゲットを追加
2. NSExtensionActivationRule を設定
3. App Groups を設定
4. ShareViewController で共有データ保存
5. アプリから読み出して利用


# Page. 21

![Page Image](https://bcdn.docswell.com/page/P7X36NM4EX.jpg)

実装
1. Share Extension ターゲットを追加
2. NSExtensionActivationRule を設定
3. App Groups を設定
4. ShareViewController で共有データ保存
5. アプリから読み出して利用
&lt;key&gt;NSExtensionAttributes&lt;/key&gt;
&lt;dict&gt;
&lt;key&gt;NSExtensionActivationRule&lt;/key&gt;
&lt;dict&gt;
&lt;!-- 画像データを最大5件受け入れる --&gt;
&lt;key&gt;
NSExtensionActivationSupportsImageWithMaxCount
&lt;/key&gt;
&lt;integer&gt;5&lt;/integer&gt;
&lt;!-- 文字列データを受け入れる --&gt;
&lt;key&gt;
NSExtensionActivationSupportsText
&lt;/key&gt;
&lt;true/&gt;
&lt;/dict&gt;
&lt;/dict&gt;


# Page. 22

![Page Image](https://bcdn.docswell.com/page/37KY1NGPED.jpg)

実装
1. Share Extension ターゲットを追加
2. NSExtensionActivationRule を設定
3. App Groups を設定
4. ShareViewController で共有データ保存
5. アプリから読み出して利用


# Page. 23

![Page Image](https://bcdn.docswell.com/page/LJ39QVN4J5.jpg)

実装
1. Share Extension ターゲットを追加
2. NSExtensionActivationRule を設定
3. App Groups を設定
4. ShareViewController で共有データ保存
5. アプリから読み出して利用
// extensionContextからNSItemProviderを取得
let provider = extensionContext.inputItems
.compactMap { $0 as? NSExtensionItem }
.flatMap { $0.attachments ?? [] }
.first!
// NSItemProviderからデータ・情報取得
let data =
try await provider.loadDataRepresentationAsync(
for: .image
)
// App Groupに保存
let destination = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: &quot;...&quot;
)?
.appending(path: &quot;...&quot;)
try data.write(to: destination!, options: .atomic)


# Page. 24

![Page Image](https://bcdn.docswell.com/page/8JDG98W5JG.jpg)



# Page. 25

![Page Image](https://bcdn.docswell.com/page/VEP3Z89D78.jpg)



# Page. 26

![Page Image](https://bcdn.docswell.com/page/27V4WNMGEQ.jpg)

SUBQUERY
• NSExtensionActivationSupports*を列挙
する方式では
• 無制限の受入れ
• 特定拡張子の拒否
• 独自ファイル形式の受入れができな
い
• SUBQUERY を書く必要
&lt;key&gt;NSExtensionActivationRule&lt;/key&gt;
&lt;string&gt;
SUBQUERY(
extensionItems,
$item,
SUBQUERY(
$item.attachments,
$attachment,
ANY $attachment.registeredTypeIdentifiers
UTI-CONFORMS-TO &quot;com.example.myfile&quot;
).@count == $item.attachments.@count
).@count == 1
&lt;/string&gt;


# Page. 27

![Page Image](https://bcdn.docswell.com/page/5JG1DKXD7L.jpg)

loadItem
• 唯一 NSItemProvider で async/await に
対応
• iOS 27 から deprecated
• loadFileRepresentation や
loadDataRepresentation で代替
func loadItem(
forTypeIdentifier typeIdentifier: String,
options: [AnyHashable : Any]? = nil
) async throws -&gt; any NSSecureCoding


# Page. 28

![Page Image](https://bcdn.docswell.com/page/47QD1N8XEP.jpg)

loadDataRepresentation
extension NSItemProvider {
func loadDataRepresentationAsync(for type: UTType) async throws -&gt; Data {
try await withCheckedThrowingContinuation { continuation in
_ = loadDataRepresentation(for: type) { data, error in
if let error {
continuation.resume(throwing: error)
} else if let data {
continuation.resume(returning: data)
} else {
continuation.resume(throwing: CocoaError(.fileReadUnknown))
}
}
}
}
}


# Page. 29

![Page Image](https://bcdn.docswell.com/page/KE4ZRG22J1.jpg)

Isolation Boundary
• 複数 NSItemProvider を並行処理したい場合 Task Groups を使いたい
• NSItemProvider は非 Sendable → isolation boundary を越えられない
• Swift 6 からはコンパイルエラー
• 解決策:
• Main Actor で Sendable まで処理する
• Main Actor で並行化する
• @unchecked Sendable を使う


# Page. 30

![Page Image](https://bcdn.docswell.com/page/L71RQDMK7G.jpg)

実装例
// MainActorで並行処理
await withTaskGroup(of: Void.self) { group in
for provider in providers {
group.addTask { @MainActor in
let data = try? await provider.loadDataRepresentation(for: .image)
// ...
}
}
}
// NSItemProviderをSendable化
extension NSItemProvider: @retroactive @unchecked Sendable {
// NSItemProviderをwrap
struct UncheckedSendableNSItemProvider: @unchecked Sendable {
let value: NSItemProvider


# Page. 31

![Page Image](https://bcdn.docswell.com/page/G7W1MY3PJ2.jpg)

まとめ
やりたいこと
共有元
共有先
ファイル共有
Share Sheet にファイル URL を渡す
CFBundleDocumentTypes
ファイル直接参照
openInPlace: true
Supports Documents in Place: YES
データ共有
Share Sheet にデータを渡す
Share Extension
• 共有元 sandbox 内のファイル URL は startAccessingSecurityScopedResource で security scoped URL 化
• CFBundleDocumentTypes はファイル専用なので、データにも対応するには Share Extension を用意する
• Share Extension の受け入れをカスタマイズするには SUBQUERY を書く
• loadItem は iOS 27 から deprecated
• NSItemProvider は非 Sendable


