Info.plistのDocumentTypesを足して、他アプリから簡単にファイルを受け取れるようにしてみよう

2023.01.30

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

CX事業本部の中安です。まいどです。

ブログのアイコンが新しくなりました。 こちらの写真は、先日同僚とUSJに行ったのですが、浮かれ気分で買ったスーパーマリオの帽子をつけたまま、疲れきって土産屋の片隅で黄昏れる自分です。 哀愁があっていいなと思いました。

さて今回ですが、さまざまなiPhoneアプリでは他のアプリにファイルを共有する機能が備わっています。

その際には、こちらのようなメニュー画面が表示され、共有する先のアプリを指定すれば簡単に渡すことができます。 これを自分のアプリに実装する方法を簡単に書いていこうと思います。

ドキュメントタイプを足す

実は、他のアプリからファイルの共有を受け入れるのは難しいことではありません。 info.plist にいくつかの記述をするだけです。

ソースコード(XML)から足す

info.plist をソースコードとして開いて、以下のような辞書(dict)を配列の一要素として足します。

info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
:
中略
:
<key>CFBundleDocumentTypes</key>
<array>
	<dict>
		<key>CFBundleTypeName</key>
		<string>Audio Files</string>
		<key>LSHandlerRank</key>
		<string>Default</string>
		<key>LSItemContentTypes</key>
		<array>
			<string>public.audio</string>
			<string>public.mp3</string>
			<string>public.mpeg-4-audio</string>
		</array>
	</dict>
</array>

辞書(dict)の中身ですが、3つのキーがあります。

  • CFBundleTypeName: これは開発者が認識しやすいように名前をつけています。今回は音声ファイルを受け取る例を示しているので「Audio Files」と名づけています。画像ファイルの場合は「Image Files」などとしておけばよいでしょう。
  • LSHandlerRank: ドキュメントのサポートレベルです。今回はとりあえずDefaultにします。詳しくは過去のDevelopersIOブログなど参照ください。
  • LSItemContentTypes: こちらに受け取りたいファイルの種別を指定します。音声の他に画像や動画など色々と設定できます。見ていただいた通り文字列の配列で設定され、複数設定できます。

ここに書くのは文字列になりますが、何を書いていいかわからないと思います。 この対応表についてはAppleのドキュメントに対応票があるので、ここから拡張子を元に検索してみてください。

Uniform Type Identifiers Reference

Xcode上から足す

info.plistのXMLに直接足すのが早いですが、Xcode上の設定からも足すことができます。

TARGETS > info > Document Types(n) に進み、「+」を押して、先ほど同様の設定値を書いていくだけです。 TypesLSItemContentTypesのように配列となっていて、複数設定する場合はカンマで区切ります。

例: public.audio,public.mp3,public.mpeg-4-audio

実際にファイルを受け取る

実際にファイルを受け取る流れは、別アプリから先ほどのメニュー画面が開かれ、共有先のアプリを選択すると、そのアプリは起動することになります。

その起動時に送られるファイルの場所が取得できるので、それを適宜な処理で捌いてやるだけです。

では具体的にソースコードで見ます。実装箇所はAppDelegateです。

AppDelegate.swift

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
:
(中略)
:
    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        // ここで url.path を参照すると渡されたファイルの場所がわかる
        return true
    }
}

ファイルを渡された状態でアプリが起動すると、上記のapplication(_:open:options:)が呼ばれます。 コメントにある通り、url.pathを見てみると「インボックス」と呼ばれる端末上のファイル置き場にあるファイルパスが取得できるので、 FileManagerを使用してファイルを然るべき場所にコピーします。

渡されたファイルを使ってそのまま破棄するのであれば「一時ディレクトリ」、恒久的に保存するのであれば「ドキュメントディレクトリ」などが適しているかと思います。

また、この手法でアプリが起動した場合はoptionsUIApplication.OpenURLOptionsKey.openInPlaceというキーで値が入っているので、別の処理と混合しないようにこれを使ってうまく切り分けてやる必要もありそうです。

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    if let _ = options[.openInPlace] {
        // ファイルが共有されたときの処理    
    }
    return true
}

警告が出るとき

ここまでの実装で他アプリからファイルを受け取る実装はできたのですが、こういった警告が出てきてしまいます。

The application supports opening files, but doesn't declare whether it supports opening them in place. You can add an LSSupportsOpeningDocumentsInPlace entry or an UISupportsDocumentBrowser entry to your Info.plist to declare support.
 
アプリケーションはファイルを開くことをサポートしていますが、その場で開くことをサポートしているかどうかは宣言されていません。
Info.plist に LSSupportsOpeningDocumentsInPlace エントリまたは UISupportsDocumentBrowser エントリを追加して、サポートを宣言することができます。

info.plistへの設定が足りないと言われているので足します。 この設定は他アプリから自アプリにドキュメントディレクトリの中身を見せるかどうか等の設定なので、今回は拒否します。 (アプリによって仕様が違うと思うので、このあたりは読み替えてください)

info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
:
中略
:
    <key>CFBundleDocumentTypes</key>
    <key>UISupportsDocumentBrowser</key>
    <false/>
    <key>LSSupportsOpeningDocumentsInPlace</key>
    <false/>

警告にある通り、どちらかを設定すれば警告は消えます。

最後に

他アプリとのファイルのやりとりは、アプリの機能に幅が出てきますね。

アプリの特性を活かして、便利なアプリを世に放っていきましょう。

ではまたー