iOS Swift

【Swift】カメラロールの写真を一覧表示する

2022年2月13日

Pohtos.frameworkでiPhone本体のカメラロールにアクセスし、UICollectionViewで一覧表示する方法を説明します。


PhotosUI.frameworkのPHPickerViewControllerを使う方法

iOS14から追加されたPHPickerViewControllerを使うと簡単に写真アプリから写真の取得が可能になります。

(写真アプリへのアクセス許可不要、画面の実装も不要)

しかし画面のカスタマイズが必要な場合などには不向きです。

【Swift】PhotosUIのPHPickerViewControllerで写真を取得する

iOS14から簡易的な方法で写真アプリから写真データの取得が可能になりました。(写真へのアクセス許可不要、画面の実装不要) 今回はPhotosUI.frameworkのPHPickerViewCont ...




Pohtos.frameworkを使ってUICollectionViewで一覧表示する方法

まずはInfo.plistに写真アプリにアクセスするための設定を追加します。

Info.plistを右クリック > Open As > Source Codeで開く

下記コードを追加する

<key>NSPhotoLibraryUsageDescription</key>
<string>(写真の利用目的を記載する)</string>


写真アプリへのアクセス許可リクエスト

Photos.frameworkをインポート

import Photos


下記コードで写真アプリへのアクセス許可状態のチェックと、未確認の場合許可ダイアログの表示ができます。

(非同期処理のためDispatchSemaphoreで同期させています)

func requestPhotoLibraryAuthorization() -> Bool {
    var isAuthorized: Bool = false
    
    let semaphore: DispatchSemaphore = DispatchSemaphore.init(value: 0)
    
    PHPhotoLibrary.requestAuthorization({ status in
        isAuthorized = status == .authorized
        semaphore.signal()
    })
    
    semaphore.wait()
    
    return isAuthorized
}


上記メソッドを実行すると下のようなダイアログが表示されます。(一度表示されると2回目以降は表示されません)


許可ステータスの種類

  • .authorized :全て許可状態
  • .denied :アクセス拒否状態
  • .notDetermined :未確認状態
  • .restricted :ユーザーが許諾する権限を持っていない状態
  • .limited :一部のみ許可状態

requestAuthorization(_ handler: @escaping (PHAuthorizationStatus) -> Void)で許可リクエストを行った場合、「写真を選択...」を選んでも.limitedにはならずに.authorizedになります。
requestAuthorization(for accessLevel: PHAccessLevel, handler: @escaping (PHAuthorizationStatus) -> Void)PHAccessLevelに.addOnlyまたは.readWriteを指定した場合は.limitedが適用されます。


2回目以降で.authorizedじゃないときは下記コードで設定アプリに誘導してあげると親切です。

// 設定アプリを開く
if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
    UIApplication.shared.open(url, options: [:], completionHandler: nil)
}





画面を作成

レイアウトの組み方については省略しますが、UICollectionViewに画像表示用にカスタムセルとしてImageCellを登録している状態です。



カメラロールへのアクセス

下記コードでカメラロールから全ての写真データをPHAssetの配列として取得することができます。

/// カメラロールから全て取得する
private func camerarollAssets() -> [PHAsset] {
    var photoAssets: Array! = [PHAsset]()
    let assets: PHFetchResult = PHAsset.fetchAssets(with: .image, options: nil)
    assets.enumerateObjects({ (asset, index, stop) -> Void in
        photoAssets.append(asset as PHAsset)
    })
    return photoAssets.reversed()
}


下記コードでPHAssetからUIImageを取得することができます。

/// サムネイルを取得する
private func thumbnail(asset: PHAsset) -> UIImage? {
    var assetImage: UIImage?
    
    let manager = PHImageManager()
    let options = PHImageRequestOptions()
    options.deliveryMode = .opportunistic
    options.resizeMode = .fast
    options.isSynchronous = true
    options.isNetworkAccessAllowed = true
    manager.requestImage(for: asset,
                         targetSize: CGSize(width: 100, height: 100),
                         contentMode: .aspectFill,
                         options: options,
                         resultHandler: { (image, info) in
        assetImage = image
    })
    
    return assetImage
}

オリジナルサイズの画像を取得してしまうと、サイズが大きすぎて読込みに時間がかかってしまいスクロールがカクついたりするので必ず小さいサイズのサムネイルデータを取得するようにしましょう。
※オリジナルサイズの画像の取得方法は本記事の最後に紹介しています。


UICollectionViewで表示すると以下のようになります。



全体のソースコード

写真へのアクセス許可を行った後で下記ViewControllerに遷移するようにしてください。

import UIKit
import Photos

class ViewController: UIViewController {
    
    @IBOutlet weak var collectionView: UICollectionView!

    private var photos: [PHAsset] = [] {
        didSet {
            collectionView.reloadData()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        photos = camerarollAssets()
    }
    
    /// カメラロールから全て取得する
    private func camerarollAssets() -> [PHAsset] {
        var photoAssets: Array! = [PHAsset]()
        let assets: PHFetchResult = PHAsset.fetchAssets(with: .image, options: nil)
        assets.enumerateObjects({ (asset, index, stop) -> Void in
            photoAssets.append(asset as PHAsset)
        })
        return photoAssets.reversed()
    }
    
    /// サムネイルを取得する
    private func thumbnail(asset: PHAsset) -> UIImage? {
        var assetImage: UIImage?
        
        let manager = PHImageManager()
        let options = PHImageRequestOptions()
        options.deliveryMode = .opportunistic
        options.resizeMode = .fast
        options.isSynchronous = true
        options.isNetworkAccessAllowed = true
        manager.requestImage(for: asset,
                                targetSize: CGSize(width: 100, height: 100),
                                contentMode: .aspectFill,
                                options: options,
                                resultHandler: { (image, info) in
            assetImage = image
        })
        
        return assetImage
    }
}

extension ViewController: UICollectionViewDataSource {

    /// セル数
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return photos.count
    }

    /// セル返却
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell:ImageCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell
        let image = thumbnail(asset: photos[indexPath.item])
        cell.setImage(image: image)
        return cell
    }
    
}

extension ViewController: UICollectionViewDelegate {

    /// タップイベント
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        // プレビュー画面で表示させたりする
    }
    
}

extension ViewController: UICollectionViewDelegateFlowLayout {
    
    /// セルサイズ
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // 横に4列表示する
        let cellWidth:CGFloat = collectionView.bounds.width / 4
        let cellHeight:CGFloat = cellWidth
        return CGSize(width: cellWidth, height: cellHeight)
    }
    
}

/// 画像セル
class ImageCell: UICollectionViewCell {
    @IBOutlet weak var imageView: UIImageView!
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
    
    func setImage(image: UIImage?) {
        self.imageView.image = image
    }
}





(おまけ)オリジナルサイズの画像の取得方法

/// オリジナルサイズ画像を取得する
private func originalImage(asset: PHAsset) -> UIImage? {
    var assetImage: UIImage?

    let options = PHImageRequestOptions()
    options.isSynchronous = true
    options.isNetworkAccessAllowed = true
    PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { imageData, dataUTI, orientation, info in
        if let imageData = imageData {
            assetImage = UIImage.init(data: imageData)
        }
    }

    return assetImage
}
【Swift】画像のプレビュー画面の実装方法

いい感じに拡大縮小表示ができる画像のプレビュー画面の実装方法を紹介します。 画面のレイアウト UIScrollViewを画面いっぱいに表示されるように設置してください。 (コンテンツのサイズが確定して ...



  • この記事を書いた人

ツシマ ショウヘイ

フリーランスのiOS/Androidアプリエンジニア。 自作アプリがストアのカテゴリ別ランキングで2位を達成!! 自分用のメモを兼ねてブログを始めました。