📤

Share Extensionに渡されたファイルが読み取れなかった問題を解消した

に公開

Share Extensionとは

Share Extensionは、他のアプリで共有したいデータを自分のアプリで受け取るための機能です。例えば、写真アプリで写真を選択し、共有ボタンを押すと、Share Extensionが起動し、その写真を自分のアプリで受け取ることができます。

問題の発生

元の実装では、以下のコードを使用して画像ファイルを取得していました。

let extensionItem = extensionContext!.inputItems[0] as! NSExtensionItem
let attachment = extensionItem.attachments![0]
attachment.loadFileRepresentation(forTypeIdentifier: "public.image", completionHandler: { url, error in
    let tempDirectory = try FileManager.default.url(
        for: .itemReplacementDirectory,
        in: .userDomainMask,
        appropriateFor: url,
        create: true
    )
    let destURL = tempDirectory.appending(path: url.lastPathComponent)
    try FileManager.default.copyItem(at: url, to: destURL)
}

このコードでは、取得したURLを一時ディレクトリに移動し、そのURLを使用して画像を表示していました。しかし、ある日、Duolingoから共有された画像を受け取った際に、渡されたURLをもとにUIImageを生成しようとしたところ、nilになってしまう問題が発生しました。

原因の調査

問題を調査した結果、Duolingoから渡されたファイルには拡張子がついていないことが判明しました。LLDBを使用してデータの先頭をダンプしてみたところ、以下のようなデータが表示されました。

po data.map { String(format: "%02x", $0) }.joined()

結果:

62706c6973743030d4010203040506070a582476657273696f6e592461726368697665725424746f7058246f626a6563747312000186a05f100f4e534b657965644172636869766572d1080954726f6f748001a90b0c2e2f3034383f4355246e756c6cdf

このデータを調べたところ、Binary Plist形式であることが分かりました。Binary Plistは、NSKeyedArchiverでエンコードされたデータである可能性があります。

解決方法

以下のコードを使用して、Binary Plist形式のデータをアンアーカイブし、UIImageを取得することができました。

let extensionItem = extensionContext!.inputItems[0] as! NSExtensionItem
let attachment = extensionItem.attachments![0]
attachment.loadFileRepresentation(forTypeIdentifier: "public.image", completionHandler: { url, error in
    let data = try! Data(contentsOf: url!)
    let image = try! NSKeyedUnarchiver.unarchivedObject(ofClass: UIImage.self, from: data)
}

このコードでは、Dataを取得した後、NSKeyedUnarchiverを使用してデータをアンアーカイブしています。これにより、拡張子がない場合でも正しく画像を取得することができました。

発展

次のようなextensionを生やすことで、ヘッダーを確認し、Binary Plist形式であるかどうかを判定することができます。

extension Data {
    var isBinaryPlist: Bool {
        let magic = "bplist00".data(using: .ascii)!
        return self.prefix(8) == magic
    }
}

まとめ

TypeIdentifierが"public.image"に準拠していても、Binary Plist形式である可能性があるため、データの形式を確認することが重要です。一度データをアンアーカイブし、それに失敗する場合にファイルをコピーしたり、拡張子で判断するなどの処理を行うことが必要です。この問題を解決することで、Share Extensionを通じて受け取った画像を正しく表示できるようになりました。

Discussion