質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.61%

share extensionのdidSelectPostにおいて,画像の取得ができません

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,020

score 16

前提・実現したいこと

share extensionを利用して画像を共有したいと思っています.
webページで開いている画像等ではなく,iphoneの写真アプリ内の画像やスクリーンショットの画像を共有することを想定しております.
didSelectPostにおいて,postがクリックされた際にアタッチメントの画像をシェアしたいのですがうまくいきません.

発生している問題・エラーメッセージ

エラーは起こっていませんが,共有したい画像の取得ができません.

該当のソースコード

    override func didSelectPost() {

        let inputItem: NSExtensionItem = self.extensionContext?.inputItems[0] as! NSExtensionItem
        let itemProvider = inputItem.attachments![0]


        // 画像を取得
        if (itemProvider.hasItemConformingToTypeIdentifier("public.url")) {
            itemProvider.loadPreviewImage(options: nil, completionHandler: { (item, error) in
                if let image = item as? UIImage {
                    //キーチェーンで紐付いている名前を記入
                    let sharedDefaults: UserDefaults = UserDefaults(suiteName: self.suiteName)!
                    //画像を保存 ※ここで画像の保存がうまくいきません
                    sharedDefaults.setValue(image.pngData(), forKey: "img")
                    //画像の名前を保存
                    var imgName = self.textView.text
                    imgName = ShareViewController.valueOfCell + "_" + imgName!
                    sharedDefaults.set(imgName, forKey: "name")
                    sharedDefaults.synchronize()
                }
            })
        }

        self.extensionContext?.completeRequest(returningItems: [], completionHandler:nil)
    }

保存がうまくいったか確認するコード

    class ImageViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        //確認
        let suiteName: String = "group..." //group名は適当です
        let imgkey: String = "img"
        let namekey: String = "name"
        let sharedDefaults: UserDefaults = UserDefaults(suiteName: suiteName)!
        let img = sharedDefaults.object(forKey: imgkey)
        let name = sharedDefaults.object(forKey: namekey)
        print(name as! String)
        print(img)  


    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

試したこと

didSelectPostを以下のように変えたところ,画像の名前(文字列)の保存のみできました.
以下のコードで保存ができてるかImageViewControllerで確認したところ,print(img)でOptional(teklra;)と出力されました.
ちなみに,「//画像を保存」と「画像の名前を保存」に該当するコードの順序を逆にして実行すると(画像を保存してから画像の名前を保存しようとすると),画像の名前すら保存されませんでした.

    override func didSelectPost() {

        let inputItem: NSExtensionItem = self.extensionContext?.inputItems[0] as! NSExtensionItem
        let itemProvider = inputItem.attachments![0]


       //キーチェーンで紐付いている名前を記入
       let sharedDefaults: UserDefaults = UserDefaults(suiteName: self.suiteName)!
       //画像の名前を保存
       var imgName = self.textView.text
       sharedDefaults.set(imgName, forKey: self.namekey)
       //画像を保存
       sharedDefaults.setValue(inputItem, forKey: "img")
       sharedDefaults.synchronize()

        self.extensionContext?.completeRequest(returningItems: [], completionHandler:nil)
    }

どうぞよろしくお願い致します.

補足情報(FW/ツールのバージョンなど)

Swift4
Xcode10.1

ここにより詳細な情報を記載してください。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • takabosoft

    2019/01/08 14:26

    share extensionやったことがないのですが、「//キーチェーンで紐付いている名前を記入」あたりのコードにまで制御が到達しているのは確認できているのでしょうか?
    例えば画像の保存はできていないが、ファイル名の方は取れていたるするのでしょうか?

    また、うまく保存できたかどうかはどのようなコードで確認されているのでしょうか?

    キャンセル

  • kj8

    2019/01/08 14:41

    説明不足で申し訳ございません.
    「//キーチェーンで紐づいている名前を記入」のところまで制御が到達しておりません.
    試したことに詳細を追記いたしますのでご確認お願い致します.
    うまく保存できたかどうかを確認するコードもソースコードに追加いたします.

    キャンセル

回答 1

checkベストアンサー

0

いくつかググってみて気になった点ですが、

以下の記事にimageを取得するサンプルコードが載っていました。
https://qiita.com/KosukeQiita/items/994693da551a7101cc9c#imageを取得する

で、self.extensionContext?.completeRequest(returningItems: [], completionHandler:nil)
の位置がサンプルと違うようです。

たぶんですが、何かしらの処理が成功したか失敗したかをどこかのタイミングで通知する必要があり、
本来であればすべて画像なりなんなりを保存した上でself.extensionContext?.completeRequestを呼び出さないといけないはずが、
何も保存できていない段階でself.extensionContext?.completeRequestを呼び出していて処理が正常に動作していないのかな?と感じました。

失敗した時にはself.extensionContext!.cancelRequestを呼び出した方が良いのかなと思いましたが、そのへんは調べてみてください。


追記:
自分でプロジェクト作ってみましたがシミュレーター上では保存コードまで到達しています。
(googleのトップページをpostしているだけです)

イメージ説明

ちゃんとブレークしていますし、OKもprintされています。

class ShareViewController: SLComposeServiceViewController {

    let suiteName = "group..."

    override func isContentValid() -> Bool {
        // Do validation of contentText and/or NSExtensionContext attachments here
        return true
    }

    override func didSelectPost() {
        let inputItem: NSExtensionItem = self.extensionContext?.inputItems[0] as! NSExtensionItem
        let itemProvider = inputItem.attachments![0]


        // 画像を取得
        if (itemProvider.hasItemConformingToTypeIdentifier("public.url")) {
            itemProvider.loadPreviewImage(options: nil, completionHandler: { (item, error) in
                if let image = item as? UIImage {
                    print("OK")
                    //キーチェーンで紐付いている名前を記入
                    let sharedDefaults: UserDefaults = UserDefaults(suiteName: self.suiteName)!
                    //画像を保存 ※ここで画像の保存がうまくいきません
                    sharedDefaults.setValue(image.pngData(), forKey: "img")
                    //画像の名前を保存
                    var imgName = self.textView.text
                    imgName = "_" + imgName!
                    sharedDefaults.set(imgName, forKey: "name")
                    sharedDefaults.synchronize()
                }
                self.extensionContext?.completeRequest(returningItems: [], completionHandler:nil)
            })
        }


    }

    override func configurationItems() -> [Any]! {
        // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
        return []
    }

}

更に追記:

どうもXcode10とシミュレーターの組み合わせだと実行できないバグがあるようでして(ドメインのエラーが出る)
https://stackoverflow.com/questions/52462456/sharing-pdf-file-from-safari-with-action-extension-failing-in-swift-4-2-xcode-10?rq=1

It looks like it is a bug in Xcode 10 iOS 12 Simulator only.

実機で試したところ、うまく動きましたので、一度報告します。

    override func didSelectPost() {

        let inputItem: NSExtensionItem = self.extensionContext?.inputItems[0] as! NSExtensionItem
        let itemProvider = inputItem.attachments![0]

        if itemProvider.canLoadObject(ofClass: UIImage.self) {
            itemProvider.loadObject(ofClass: UIImage.self) { image, error in
                if let image = image as? UIImage {
                    let sharedDefaults = UserDefaults(suiteName: self.suiteName)!
                    sharedDefaults.setValue(image.pngData(), forKey: "img")
                }
                self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
            }
        } else {
            self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
        }
    }

更に追記:

一応他の方法も試しました。
loadItemを使うと画像のURLが取れましたが、どうも一時的なもので、このURLには確認用アプリの方からはアクセスできなさそうでした(結局そのファイルの内容をバイナリのままUserDefaultsに保存する方法で試作)。

一個上のサンプルコードは一旦UIImageに変換がかかっているので、それに比べればこちらのサンプルコードは元のファイルデータ(のコピー)をそのまま保存するので、多少無駄が減ったかなと思います。

※本当は写真のアドレスだけ他のアプリに渡して、読み取れれば容量的にもスマートなんですが、ちょっとやり方がわからなかったです。

    override func didSelectPost() {

        let inputItem: NSExtensionItem = self.extensionContext?.inputItems[0] as! NSExtensionItem
        let itemProvider = inputItem.attachments![0]

        let itemIdentifier = kUTTypeImage as String
        if itemProvider.hasItemConformingToTypeIdentifier(itemIdentifier) {
            itemProvider.loadItem(forTypeIdentifier: itemIdentifier, options: nil) { item, error in
                if let url = item as? URL {
                    print(url)
                    if let data = try? Data(contentsOf: url) {
                        let sharedDefaults = UserDefaults(suiteName: self.suiteName)!
                        sharedDefaults.set(data, forKey: "img")
                        sharedDefaults.synchronize()
                    }
                }
                self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
            }
        } else {
            self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
        }
    }

なお、itemProvider.loadDataRepresentationではDataが取れなかったので諦めました。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/01/09 10:41

    試してはいるのですが、loadPreviewImageで写真のサムネイルは取れても、オリジナル画像がどうにも取れないんですよねぇ。。。

    キャンセル

  • 2019/01/09 12:00

    いろいろ解答欄に追記しました。私の方は一旦これで調査を終わります。

    キャンセル

  • 2019/01/09 14:56

    いろいろ試していただきありがとうございました!無事画像を保存することができました.
    大変感謝しております.

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.61%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る