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

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

ただいまの
回答率

89.21%

Photos frameworkを使って端末内のアルバムの画像をUIImageの配列として取得したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,191

pftyuk

score 48

前提・実現したいこと

Photos frameworkを使って端末内のアルバムの画像をUIImageの配列として取得したいです。

こちらを参考にして、実装を行ったのですが実行時にエラーで落ちてしまいます。
調べた限りメモリリークに関するエラーのようですが、解決方法がわからなかったので
ご教示頂ければ幸いです。

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

2019-01-19 11:56:15.097534+0900 GemCamera[8198:2560115] [ImageManager] First stage of an opportunistic image request returned a non-table format image, this is not fatal, but it is unexpected
Message from debugger: Terminated due to memory issue

該当のソースコード

class AlbumViewController: UIViewController {
    var assets:[PHAsset] = []
    var selectedAsset:PHAsset? = nil

    @IBOutlet weak var albumCollectionView: GeminiCollectionView!{
        didSet{
            let cellIdentifier = "AlbumCollectionViewCell"
            let nib = UINib(nibName: cellIdentifier, bundle: nil)
            albumCollectionView.register(nib, forCellWithReuseIdentifier: cellIdentifier)
            albumCollectionView.delegate = self
            albumCollectionView.dataSource = self

            albumCollectionView.gemini
                .circleRotationAnimation()
                .radius(450)
                .rotateDirection(.clockwise)
                .itemRotationEnabled(true)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        assets = getAssetsInAlbum()

        if let layout = albumCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            layout.scrollDirection = .vertical
            albumCollectionView.collectionViewLayout = layout
        }
    }

    private func getAssetsInAlbum()->[PHAsset]{
        var assets:[PHAsset] = []

        let options = PHFetchOptions()
        let fetchResult = PHAsset.fetchAssets(with: .image, options: options)
        fetchResult.enumerateObjects { (asset, index, stop) -> Void in
            assets.append(asset)
        }

        return assets
    }

    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "toPhotoPreView"{
            let photoViewController = segue.destination as! PhotoPreViewController
            photoViewController.selectedAsset = selectedAsset
        }
    }
}

// MARK: - UIScrollViewDelegate
extension AlbumViewController {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        albumCollectionView.animateVisibleCells()
    }
}

// MARK: - UICollectionViewDelegate
extension AlbumViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        selectedAsset = assets[indexPath.row]
        performSegue(withIdentifier: "toPhotoPreView", sender: nil)
    }

    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        if let cell = cell as? GeminiCell {
            self.albumCollectionView.animateCell(cell)
        }
    }
}
// MARK: - UICollectionViewDataSource
extension AlbumViewController:UICollectionViewDataSource{
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return assets.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AlbumCollectionViewCell", for: indexPath) as! AlbumCollectionViewCell
        cell.configure(with: assets[indexPath.row])
        self.albumCollectionView.animateCell(cell)
        return cell
    }
}

// MARK: - UICollectionViewDelegateFlowLayout
extension AlbumViewController: UICollectionViewDelegateFlowLayout {
    private enum Const {
        static let collcetionViewSize = CGSize(width: UIScreen.main.bounds.size.width * 0.7, height: UIScreen.main.bounds.size.height * 0.7)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return Const.collcetionViewSize
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        guard let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
            return UIEdgeInsets(top: 50, left: 50, bottom: 50, right: 50)
        }

        switch layout.scrollDirection {
        case .horizontal:

            let verticalMargin: CGFloat = (collectionView.bounds.height - Const.collcetionViewSize.height) / 2
            return UIEdgeInsets(top: 50 + verticalMargin,
                                left: 50,
                                bottom: 50 + verticalMargin,
                                right: 50)
        case .vertical:

            let horizontalMargin: CGFloat = (collectionView.bounds.width - Const.collcetionViewSize.width) / 2
            return UIEdgeInsets(top: 50,
                                left: 50 + horizontalMargin,
                                bottom: 50,
                                right: 50 + horizontalMargin)
        }
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 10
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 0
    }
}
class AlbumCollectionViewCell: GeminiCell {
    @IBOutlet weak var image: UIImageView!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    func configure(with asset:PHAsset ) {
        var photoImage:UIImage? = nil
        let targetSize = CGSize(width: self.bounds.width, height: self.bounds.height)
        let manager = PHImageManager()
        manager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit,options: nil){ (image, info) in
            if image != nil{
                photoImage = image
            }
        }

        self.image.image = photoImage
    }
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+2

これは、アルバム内の全ての写真をフルサイズで取得して配列に格納しようとしていますから、
たくさん写真が入っているiPhoneで実行したら当然メモリ不足になると思います。
iPhoneのカメラで撮影した写真をオリジナルサイズでUIImageに読み込んだら、
4032x3024x4=約48MBのメモリを消費します。それが10枚あったら480MBの
メモリ消費です。

実際に表示が必要になった場面で必要な写真だけ読み込むように設計する必要があると思います。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/01/23 00:34 編集

    返信ありがとうございます。
    早速試してみましたが結果が変わりませんでした。

    まずself.boundsをprintしてみましたが意図しているポイント数になっていました。
    わかりやすいようにセルの大きさを固定値にして確認してみました。

    scaleを掛けたサイズをtargetSizeにとのことですが
    let scale = CGFloat(3.0)
    let targetSize = CGSize(width: self.bounds.width * scale, height: self.bounds.height * scale)
    このように変更してみました。(解釈が違っていたらご指摘下さい)

    上記で解決しないとなると、別のところに原因があるのでしょうか・・・。

    追記
    写真を表示しているセルはxibで作成しており、セルの上にImageViewが乗っているのですが
    このImageViewのcontent ModeはAspectFillになっております(関係があるかわかりませんが・・・)

    キャンセル

  • 2019/01/23 00:44

    ```
    manager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit,options: nil){ (image, info) in
    if image != nil{
    photoImage = image
    }
    }
    self.image.image = photoImage
    ```
    これって、よく見るとクロージャの外で`self.image.image`に画像を設定してますが、本当にこれで画像が表示できてるんですか?
    クロージャの中で `photoImage = image`をした直後に `self.image.image = photoImage`をする必要があると思います。

    キャンセル

  • 2019/01/23 22:32

    返信ありがとうございます。

    まさに仰る通り、クロージャの外で画像をセットしていた部分を
    クロージャ中で画像をセットするように変更したところ、うまく表示されるようになりました!
    ただ一部の画像だけ荒いままでそちらは別問題な気がしますので自分で調べてみます。

    興味の範疇なのでご存知であればお答え頂ければ幸いなのですが
    何故クロージャの外でセットした場合と中でセットした場合で違いが出たのでしょうか・・・。

    一旦こちらについて、ベストアンサーとさせて頂き閉じさて頂きます。
    この度はありがとうございました!

    キャンセル

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

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