Notice
Recent Posts
Recent Comments
Link
관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 1. 갤러리 화면 만들기, 사진 첨부 - 앨범 가져오기 (PHFetchResult, PHAsset) 본문

UI 컴포넌트 (swift)

[iOS - swift] 1. 갤러리 화면 만들기, 사진 첨부 - 앨범 가져오기 (PHFetchResult, PHAsset)

jake-kim 2023. 6. 27. 01:50

1. 갤러리 화면 만들기, 사진 첨부 - 앨범 가져오기 (PHFetchResult, PHAsset)

2. 갤러리 화면 만들기, 사진 첨부 - 사진 가져오기 (PHCachingImageManager, PHImageRequestOptions)

3. 갤러리 화면 만들기, 사진 첨부 - 갤러리 화면 UI 구현 방법

직접 구현한 사진 선택 화면

앨범과 사진 가져오는 방법

  • Photos 모듈에서 제공하는 API를 사용
    • 디바이스의 앨범을 먼저 가져오기 (PHFetchResult가 앨범을 의미)
    • 앨범에 담긴 이미지 정보 가져오기 (PHAsset이 이미지나 비디오 정보를 의미)
    • PHAsset을 가지고 UIImage 이미지 가져오기 (PHCachingImageManager가 요청한 크기에 맞추어 PHAsset으로부터 이미지를 가져옴)
    • 쿼리는 모두 PHImageRequestOptions를 작성
  • 위에서 얻은 이미지(UIImage)를 UICollectionView와 UICollectionViewFlowLayout으로 쉽게 구현이 가능

예제에 사용한 라이브러리

  • Cocoapod 사용
    • Then: sugar 프로그래밍에 도움을 주는 라이브러리
    • SnapKit: 코드베이스로 오토레이아웃을 쉽게 적용하기 위한 라이브러리
pod 'Then'
pod 'SnapKit'

사진 접근 권한 요청

  • 공용적으로 사용될 수 있으므로 별도의 Service 레이어에서 구현

(권한 요청은 갤러리에서 사진 선택 화면의 메인 기능이 아니므로 구현체는 생략)

  • 버튼을 누르면 requestAuthorization(completion:)이 호출되도록 구현
protocol PhotoAuthService {
    var authorizationStatus: PHAuthorizationStatus { get }
    var isAuthorizationLimited: Bool { get }
    
    func requestAuthorization(completion: @escaping (Result<Void, NSError>) -> Void)
}

앨범 가져오기 (PHFertchResult)

  • 앨범 가져오기 역시 여러곳에서 사용될 수 있으므로 AlbumService라는 레이어로 분리하여 기능을 구현
import Photos
import UIKit
import Then

protocol AlbumService {
    func getAlbums(mediaType: MediaType, completion: @escaping ([AlbumInfo]) -> Void)
}
  • 쿼리 NSFetchOptions에 사용될 헬퍼 property와 mediaType을 쿼리에 사용될 메소드미리 정의
final class MyAlbumService: AlbumService {
    private let getSortDescriptors = [
        NSSortDescriptor(key: "creationDate", ascending: false),
        NSSortDescriptor(key: "modificationDate", ascending: false)
    ]
        
    private func getPredicate(mediaType: MediaType) -> NSPredicate {
        let format = "mediaType == %d"
        switch mediaType {
        case .all:
            return .init(
                format: format + " || " + format,
                PHAssetMediaType.image.rawValue,
                PHAssetMediaType.video.rawValue
            )
        case .image:
            return .init(
                format: format,
                PHAssetMediaType.image.rawValue
            )
        case .video:
            return .init(
                format: format,
                PHAssetMediaType.video.rawValue
            )
        }
    }
}
  • 앨범 가져오는 방법
    • 0. albums 변수 선언
    • 1. query 설정
    • 2. standard 앨범을 query로 이미지 가져오기
    • 3. smart 앨범을 query로 이미지 가져오기
  • 0. albums 변수 선언
    • 쿼리를 날려서 앨범을 가져오는데, 이때 가져올 앨범들이 저장될 변수
final class MyAlbumService: AlbumService {
    func getAlbums(mediaType: MediaType, completion: @escaping ([AlbumInfo]) -> Void) {
        // 0. albums 변수 선언
        var albums = [AlbumInfo]()
        defer { completion(albums) }
        
        // 1. query 설정
        // 2. standard 앨범을 query로 이미지 가져오기
        // 3. smart 앨범을 query로 이미지 가져오기
    }
}
  • 1. query 설정
    • PHFetchOptions()를 사용하여 쿼리 날리기
// 1. query 설정
let fetchOptions = PHFetchOptions().then {
    $0.predicate = getPredicate(mediaType: mediaType)
    $0.sortDescriptors = getSortDescriptors
}
  • 2. standard 앨범을 query로 이미지 가져오기
    • PHAsset.fetchAsset(with:)을 사용하여 일반 앨범 가져오기
// 2. standard 앨범을 query로 이미지 가져오기
let standardFetchResult = PHAsset.fetchAssets(with: fetchOptions)
albums.append(.init(fetchResult: standardFetchResult, albumName: mediaType.title))
  • 3. smart 앨범을 query로 이미지 가져오기
    • PHAssetCollection을 사용하여 스마트 앨범을 가져오기
    • 나머지 스마트 앨범은 estimatedAssetCount의 값이 -1인 경우 (NSNotFount) 추가로 가져오는 코드도 필요
// 3. smart 앨범을 query로 이미지 가져오기
let smartAlbums = PHAssetCollection.fetchAssetCollections(
    with: .smartAlbum,
    subtype: .any,
    options: PHFetchOptions()
)
smartAlbums.enumerateObjects { [weak self] phAssetCollection, index, pointer in
    guard let self, index <= smartAlbums.count - 1 else {
        pointer.pointee = true
        return
    }
    
    // 스마트 앨범인 경우 (Asset count가 -1인 경우)
    if phAssetCollection.estimatedAssetCount == NSNotFound {
        let fetchOptions = PHFetchOptions().then {
            $0.predicate = self.getPredicate(mediaType: mediaType)
            $0.sortDescriptors = self.getSortDescriptors
        }
        let fetchResult = PHAsset.fetchAssets(in: phAssetCollection, options: fetchOptions)
        albums.append(.init(fetchResult: fetchResult, albumName: mediaType.title))
    }
}

앨범 가져오는걸 사용하는 쪽

  • PhotoViewController라는 곳의 viewDidLoad에서 아래처럼 호출하여 사용
    • 위에서 살펴본 앨범을 가져오는 기능을 사용하여 앨범들을 가져오고, 그 앨범들에서 [PHAsset]를 불러오는 것
    • PHAsset정보가 있으면 UIImage를 가져올 수 있으므로 UI에 이 UIImage를 그리도록하면 완성
// PhotoViewController.swift

private let albumService: AlbumService = MyAlbumService()
private var albums = [PHFetchResult<PHAsset>]()

override func viewDidLoad() {
    super.viewDidLoad()
    
    setupUI()
    loadAlbums(completion: { [weak self] in
        self?.loadImages()
    })
}

private func loadAlbums(completion: @escaping () -> Void) {
    albumService.getAlbums(mediaType: .image) { [weak self] albumInfos in
        self?.albums = albumInfos.map(\.album)
        completion()
    }
}

private func loadPHAssetsFromAlbums() {
	// TODO...
}

(가져온 앨범을 토대로 사진을 가져오고 UI에 그리는 방법은 다음 포스팅 글에서 계속)

 

* 전체 코드: https://github.com/JK0369/ExPhoto.git

Comments