Share Extensionで画像を保存する

はじめに

Share Extensionを使って写真アプリなどから画像を送って保存する方法について調べて実装しました。
Shared Extensionの記事はどれも古く、そもそも情報自体が間違っているものもあってだいぶハマりました。うるおいらんどの人がソースコード公開していなかったら詰んでました…

Share Extensionの導入までは以前書いたShare Extensionで共有ボタンが押された時アプリを表示するを参考にしてください。

コード

受け取るファイルを絞りたいときはplistでの指定が可能です。詳しくは公式ドキュメントを参照してください

保存表示

App Extension Keysを直で文字列表記しても動きますがせっかく用意されているのでMobileCoreServicesを使って定義します

import MobileCoreServices

UserDefaultsで値の保存を行っていますが通常のUserDefaults(UserDefaults.standard)では値を共有することができません。 ShareExtensionはTargetが別になります、Targetが違えば別アプリの様なものなので、別アプリのUserDefaultsを参照することはできません。

しかし通常のUserDefaultsを使った場合はAppData/Libary/Preferences直下にUserDefaultの値が保存されますが、suiteNameを指定して保存したUserDefaultsはAppGroupフォルダの中に保存されます。

AppGroupフォルダはAppGroupを許可したアプリ同士であれば相互に参照可能なためデータの共有ができるようになります。

override func didSelectPost() {
        let extensionItem: NSExtensionItem = self.extensionContext?.inputItems.first as! NSExtensionItem
        let itemProvider: NSItemProvider = extensionItem.attachments!.first!
        //appGroupで登録した文字列
        let suiteName: String = "group.sample.ImageShare"
        
        // shareExtension で NSURL を取得
        if itemProvider.hasItemConformingToTypeIdentifier(String(kUTTypeImage)) {
            itemProvider.loadItem(forTypeIdentifier: String(kUTTypeImage), options: nil, completionHandler: { (item, error) in
                
                if let imageURL = item as? URL{
                    do {
                        let imageData = try Data(contentsOf: imageURL)
                        //画像データの保存
                        let userDefault = UserDefaults(suiteName: suiteName)
                        userDefault?.set(imageData, forKey: "img")
                        userDefault?.synchronize()
                        
                    }catch {
                        print(error)
                    }
                }
                self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
            })
        }
    }

受け取り

    override func viewDidLoad() {
        super.viewDidLoad()
        let suiteName: String = "group.sample.ImageShare"
        
        let userDefaults = UserDefaults(suiteName: suiteName)
        if userDefaults?.object(forKey: "img") != nil {
            let imageData = userDefaults?.data(forKey: "img")
            image.image = UIImage(data:imageData!)!
        } else {
            print("値が保存されていません")
        }
    }

おまけ

保存

FileManagerでもAppGroup間のファイル 共有が可能です。

if let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: suiteName)?.appendingPathComponent("file_name") {
    try? imageData.write(to: url)
}

参照

let suiteName: String = "group.sample.ImageShare"

if let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: suiteName)?.appendingPathComponent("file_name") {
    do {
        let imageData = try Data(contentsOf: url)
        image.image = UIImage(data: try Data(contentsOf: url))
    }catch {
        print("失敗")
    }
}

simulator内のデータの検索

suiteNameで保存したUserDefaultの場所検索ができます。

 cd ~/Library/Developer/CoreSimulator/Devices/
 cd ${シミュレータのid}
 find ./ -name "group.sample.ImageShare.plist"
 open ${上で出てきたpath}

参考文献

uruly/AppApp

【Swift, App Group】アプリ間でデータを共有する