59000


Swift言語での開発で、以下の記事を参考にしています。

[Swift3で動画をカメラから選択してきて描画して再生まで -
Qiita](https://qiita.com/ryosukehujisawa/items/aa6e97de7d84a1658a0e)

現在，"importButton"で動画のみが表示されたカメラロールを表示し，選択するところまでは，できているのですが，その後UIImageviewに反映されません．

動画を選択すると

[code]

    2019-01 18:13:24.978844+0900 sport camera[16106:5106135] [discovery] errors encountered while discovering extensions: Error Domain=PlugInKit Code=13 "query cancelled" UserInfo={NSLocalizedDescription=query cancelled}
    
[/code]

とデバッグには表示されています．

解決方法があれば，教えていただきたいです．

[code]

    import UIKit
    import AVKit
    import AVFoundation
    
    class ViewController: UIViewController,UIImagePickerControllerDelegate,UINavigationControllerDelegate {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
        }
    
        let imagePickerController = UIImagePickerController()
        var videoURL: URL?
    
    
        @IBOutlet weak var imageView: UIImageView!
        @IBAction func importButton(_ sender: Any) {
            print("UIBarButtonItem。カメラロールから動画を選択")
            imagePickerController.sourceType = .photoLibrary
            imagePickerController.delegate = self
            imagePickerController.mediaTypes = ["public.movie"]
            present(imagePickerController,animated: true,completion: nil)
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
        private func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String:Any]) {
            videoURL = info["UIImagePickerControllerReferenceURL"] as? URL
            print(videoURL!)
            imageView.image = previewImageFromVideo(videoURL!)!
            imageView.contentMode = .scaleAspectFit
            imagePickerController.dismiss(animated: true, completion: nil)
        }
    
        func previewImageFromVideo(_ url:URL) -> UIImage? {
            print("動画からサムネイルを生成")
            let asset = AVAsset(url: url)
            let imageGenerator = AVAssetImageGenerator(asset: asset)
            imageGenerator.appliesPreferredTrackTransform = true
            var time = asset.duration
            time.value = min(time.value, 2)
            do{
                let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil)
                return UIImage(cgImage: imageRef)
            } catch {
                return nil
            }
        }
    
    }
    
[/code]


CC BY-SA 4.0
2019-09-11T09:18:10.097
58990
2020-05-21T13:41:01.893
2019-09-11T12:39:31.933
3060
35822
question
swift xcode avfoundation
Swiftでカメラロールから動画を取得して，UIImageViewに反映させたいです


リンク先の参考記事、Swift3の注記がありますから、変化の早いSwiftからすると太古の大昔の記事ということになります。またこの間にiOSのPhoto
Libraryに関する取り扱いや`UIImagePickerController`の動作も変化しました。

ご自分でこれらの変化を調べて解決できないのであれば、極端に古い記事(もう一度言いますがSwift3は極端に古いです)を参考にするのはやめておいた方が良いでしょう。自分のズバリやりたいことを解説してくれる記事で最新のSwift/iOSに対応したものを見つけるのは難しいかもしれませんが、「修行期間中」だと思って、ズバリやりたいこととはちょっと違うことで経験を重ねた方が良いと思います。

前置きはこれくらいにして本題へ。

* * *

正直言って現在の観点からはツッコミどころ満載のコードになってしまってます。「もう少しお行儀の良いコードだったらXcodeの助けを借りて動くようにできたかも…」と言うところですが、かなり「汚い」コードなので、その辺の助けも期待できませんね。

### 1\. `UIImagePickerControllerDelegate`の必要メソッドが実装されていない

あなたの(と言うより「参考記事の」ですが)コードでは、

[code]

        private func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String:Any]) {
            //...
        }
    
[/code]

と言うメソッドを実装していますが、現在のSwift/iOSでは、これは`UIImagePickerControllerDelegate`のメソッド実装ではありません。

(そのせいで、せっかくビデオを選択しても、選択をキャンセルしたことになっている。質問文中の「表示」は、キャンセル時に表示されるメッセージ(iOSのバージョンで異なるが)と全く同じ。)

そもそも(`@objc`無しで)`private`を付けてしまうと、iOSからは絶対にdelegateメソッドとして認識されません。Xcodeが「`private`付けるか?」なんてサジェスチョンを出したかもしれませんが、ここはそれに対してノーと言うべきところです。

現在のiOS/Swiftで、`UIImagePickerControllerDelegate`を実装するには、

[code]

        func imagePickerController(_ picker: UIImagePickerController,
                didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            //...
        }
    
[/code]

と言うメソッドにしなければいけません。

## 2\. `UIImagePickerControllerReferenceURL`は使用非推奨

そもそも`"UIImagePickerControllerReferenceURL"`なんて文字列定数でキーを指定してはいけません。かならず`UIImagePickerControllerReferenceURL`と言うシンボル定数を使ってください。

但し、今のiOSでは、その定数は`UIImagePickerController.InfoKey.referenceURL`なんてものに変わっている上にdeprecated(廃止を前提とした非推奨)です。iOS
11以降ではユーザの設定や状態によっては、`info`にはそのキーに対する値は入っていない可能性があります。(したがって、`videoULR!`のような強制アンラップはやってはダメ、アプリがクラッシュする。)

## 3\. 代わりは`UIImagePickerController.InfoKey.phAsset`、でも色々注意が必要

`UIImagePickerController.InfoKey.referenceURL`と言う定数を使おうとすると、Xcodeが「代わりに`UIImagePickerController.InfoKey.phAsset`を使え」(と言う意味のこと)を言ってきます。

但し、これを使うには色々と注意が必要です。

  * 使えるのはiOS 11.0以降
  * 値は`URL`型ではなく、`PHAsset`型 (`URL`とは色々勝手が違う)
  * Info.plistにPhoto Library使用のプライバシー設定が必要
  * アプリのどこかで、Photo Library使用の権限確認と権限要求が必要

* * *

と言うわけで、以上のようなことを踏まえてあなたのコードを書き直すとこんな感じになります。

[code]

    import UIKit
    import CoreServices
    import Photos
    
    class ViewController: UIViewController,
    UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            //PhotoLibrary使用の権限確認と依頼
            if PHPhotoLibrary.authorizationStatus() != .authorized {
                PHPhotoLibrary.requestAuthorization {status in
                    if status != .authorized {
                        print("not authorized")
                        //...ユーザにPhotoLibraryへのアクセス承認を促すメッセージの表示等を行う
                    }
                }
            }
        }
    
        let imagePickerController = UIImagePickerController()
    
        @IBOutlet weak var imageView: UIImageView!
    
        @IBAction func importButton(_ sender: Any) {
            print("UIBarButtonItem。カメラロールから動画を選択")
            imagePickerController.sourceType = .photoLibrary
            imagePickerController.delegate = self
            imagePickerController.mediaTypes = [kUTTypeMovie as String]
            present(imagePickerController, animated: true, completion: nil)
        }
    
        func imagePickerController(_ picker: UIImagePickerController,
                didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if #available(iOS 11.0, *) {
                //PHAssetが取れればそれを使う
                if let asset = info[.phAsset] as? PHAsset {
                    previewImage(fromVideo: asset) {image in
                        DispatchQueue.main.async {
                            self.imageView.image = image
                            self.imageView.contentMode = .scaleAspectFit
                            self.imagePickerController.dismiss(animated: true, completion: nil)
                        }
                    }
                    return
                }
            }
            //PHAssetが取れないときは(deprecatedでも取れるときは取れるので).referenceURLを試す
            guard let videoURL = info[.referenceURL] as? URL else { //ここで警告が出るが無視
                print(".referenceURL is nil")
                return
            }
            print(videoURL) //<-危険な強制アンラップは可能な限り避ける
            guard let image =  previewImageFromVideo(videoURL) else {
                print("previewImageFromVideo(\(videoURL)) is nil")
                return
            }
            imageView.image = image
            imageView.contentMode = .scaleAspectFit
            imagePickerController.dismiss(animated: true, completion: nil)
        }
    
        //ビデオのURLからサムネイル画像を作成
        func previewImageFromVideo(_ url:URL) -> UIImage? {
            print("動画からサムネイルを生成(URL)")
            let asset = AVAsset(url: url)
            let imageGenerator = AVAssetImageGenerator(asset: asset)
            imageGenerator.appliesPreferredTrackTransform = true
            var time = asset.duration
            time.value = min(time.value, 2)
            do {
                let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil)
                return UIImage(cgImage: imageRef)
            } catch {
                print(error) //エラーを黙って捨ててしまってはいけない
                return nil
            }
        }
        //ビデオのPHAssetからサムネイル画像を作成
        func previewImage(fromVideo videoAsset: PHAsset, completion: @escaping (UIImage?)->Void) {
            print("動画からサムネイルを生成(PHAsset)")
            let manager = PHImageManager.default()
            manager.requestAVAsset(forVideo: videoAsset, options: nil) {asset, audioMix, info in
                guard let asset = asset else {
                    print("asset is nil")
                    return
                }
                let imageGenerator = AVAssetImageGenerator(asset: asset)
                imageGenerator.appliesPreferredTrackTransform = true
                var time = asset.duration
                time.value = min(time.value, 2)
                do {
                    let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil)
                    completion(UIImage(cgImage: imageRef))
                } catch {
                    print(error) //エラーを黙って捨ててしまってはいけない
                    completion(nil)
                }
            }
        }
    }
    
[/code]

  * 「Info.plistにPhoto Library使用のプライバシー設定」については、わからなければネットで検索してみてください。
  * ターゲットバージョンによっては、ビルドに失敗したり、警告が出たりします。警告は無視することにして、適当にターゲットバージョンをいじってみてください。

(よくわからなければ、コメントなどでお知らせ下さい。)

Xcode 10.3でビルドし、手元のiPhone 7+ (iOS
12.3.1)で動作確認はしていますが、細かい条件等で明示できていない事柄もあるかもしれません。お試しの上、うまくいかない場合、わからない点がある場合などはお尋ね下さい。


CC BY-SA 4.0
2019-09-11T13:59:34.257
59000
2019-09-11T13:59:34.257
13972
58990
answer


まず、[OOPerさんの回答](https://ja.stackoverflow.com/a/59000/19110)はたいへん参考になりました。

  1. Swift5 / iOS13.4でPhotosライブラリの写真を選択(クリック)したあと、何が返ってくるか試してみました。

> func ExecSetPhoto(_ picker: UIImagePickerController,
> info:[UIImagePickerController.InfoKey : Any]) { print("--->", info[.phAsset]
> as? PHAsset as Any, "<\---")

  2. 結果は---> nil <\---でした。
  3. したがって、let wRefURL:URL = info[.referenceURL] as! URLを使い続けるしかないのかな(?)と考えているところです。.referenceURLなら、私のアプリが必要とするinfoの中のURLが返ってきます。参考まで。


CC BY-SA 4.0
2020-05-15T14:28:53.873
66645
2020-05-21T13:41:01.893
2020-05-21T13:41:01.893
19110
40164
58990
answer