PhotoKit으로 이미지 가져오기

Zeto·2022년 5월 8일
0

Swift_UIKit

목록 보기
3/12

어떤 프로젝트는 iOS에 내장된 앨범에 접근하여 사진이나, 영상 등의 미디어를 가져와야 하는 경우가 있다. 그럴 경우 어떻게 해야 될까?
바로 PhotoKit을 활용하면 된다. PhotoKit은 사진앱이 관리하는 이미지와 비디오 aseets을 포함하여 iCloud 이미지 및 Live 이미지를 사용하는 프레임워크 비스무리한 녀석이다. 따라서 이를 활용하면 assets를 가져오거나 캐싱을 할 수 있고, 더 나아가 편집이나 관리 등도 수월하게 할 수 있다.

물론 본 글에서는 PhotoKit에 대한 개념적인 정의를 설명하지는 않을 예정이므로 혹여 필요하신 분들은 이를 잘 정리해준 분들의 블로그를 참조하면 좋을 것 같다.
오늘은 아주 기본적으로 PhotoKit을 활용하는 방법을 작성해보고자 한다.

PhotoManager

😚 First Step.

먼저 PhotoKit을 다루기 전에 가져온 이미지들을 잘 보여줄 수 있는 틀이 필요하다. 따라서 간단하게 CollectionView를 생성해주도록 한다.

먼저 CollectionView의 크기는 화면 전체 크기에 맞추고 delegate와 datasource를 현재 viewcontroller에 위임해주도록 한다.
(cell 개수와 cell 내부 설정 등의 함수는 차후에 추가될 예정이니 현재의 컴파일 에러는 무시하자. 추가로 CollectionView 생성 시, layout 추가 필요)

우리가 만들어 놓은 CollectionView에서 사용될 커스텀 Cell을 작성하고 해당 Cell은 imageView를 가지도록 세팅해둔다.
(커스텀 Cell을 작성하지 않고도 cell 생성은 가능하니, 해당 부분은 편한 방식으로 접근하면 된다)

😚 Second Step.

먼저 우리는 앨범에 대한 접근하기 위한 권한 설정이 필요하기 때문에 info에서 관련 설정을 추가해줄 필요가 있다. 해당 설정이 잘 적용된다면, 처음으로 앨범에 접근할 때 관련 권한 요청이 Alert로 뜨게 된다.

사용자가 앨범에 대한 접근을 허용했다. 그렇다면 다음으로 우리가 할 작업은 해당 앨범에서 이미지들을 '가져오는' 작업을 이어가야 한다.

import Photos

final class PhotoManager{
    private var fetchResult: PHFetchResult<PHAsset>?
    
    func fetchImage(){
        self.fetchResult = PHAsset.fetchAssets(with: nil)
    }
}

따라서 PHAsset으로부터 assets를 fetch 해와야하며, 이를 받아올 프로퍼티의 타입은 위의 예시와 동일해야 한다.

😚 Third Step.

위에서 정리한 작업만으로도 충분히 이미지를 내보낼 수 있으면 좋겠지만, 아쉽게도 해당 작업은 이미지 자체를 가져오는 것이 아니라 메타데이터를 받아오는 것이기 때문에 PHImageManager라는 녀석에게 실제 이미지를 요청해야만 한다.

final class PhotoManager: PHCachingImageManager{
    private var fetchResult: PHFetchResult<PHAsset>?
    
    func fetchImage(){
        self.fetchResult = PHAsset.fetchAssets(with: nil)
    }
    
    func requestImage(asset: PHAsset?, thumbnailSize: CGSize, completion handler: @escaping (UIImage?) -> Void){
        guard let asset = asset else {
            let noImage = UIImage(systemName: "multiply")
            handler(noImage)
            return
        }

        self.requestImage(for: asset, targetSize: thumbnailSize, contentMode: .aspectFit, options: nil){ image, _ in
            handler(image)
        }
    }
}

ImageManager를 사용하기 위해 PHCachingImageManager를 상속받았다. 현재 커스텀으로 작성한 PhotoManager 객체 내에서 이미지 관련 작업을 모두 관리하고 있으므로 해당 작업 또한 따로 메서드를 생성하여 에러 처리도 용이하게 작성한 방법이므로, 실제 ImageManager의 requestImage 메서드를 직접 사용하고 싶은 분을 해당 방식으로 진행하여도 무방하다.

😚 Fourth Step.

이제는 가져온 이미지를 CollectionView에 차곡차곡 잘 넣기 위해서 필요한 작업들을 추가할 필요가 있다.

extension PhotoManager{
    func fetchResultCount() -> Int{
        return fetchResult?.count ?? 0
    }
    
    func getPHAsset(indexPath: IndexPath) -> PHAsset?{
        let asset = fetchResult?.object(at: indexPath.item)
        
        return asset
    }
}

먼저 PhotoManager에는 fetch된 이미지의 갯수를 반환하는 메서드와 각 인덱스에 맞는 asset을 반환해주는 메서드를 각각 작성하고, 적절한 썸네일 사이즈 또한 작성해주도록 한다.

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource{
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return imageManager.fetchResultCount()
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImageCollectionCell.identifier, for: indexPath)
        
        guard let imageCell = cell as? ImageCollectionCell, let asset = imageManager.getPHAsset(indexPath: indexPath) else { return UICollectionViewCell() }
        
        imageManager.requestImage(asset: asset, thumbnailSize: imageManager.thumbnailSize){ image in
            guard let image = image else { return }
            
            imageCell.setImage(image: image)
        }
        
        return imageCell
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 100, height: 100)
    }
}

이후 작성한 메서드를 활용하여 cell의 갯수와 각 cell에 들어갈 image를 적절하게 넣어주는 로직을 작성해준다.

다만 위와 같이 작성하게 될 경우, 비동기 문제로 인해 가져온 이미지들이 바로 출력되지 않을 수가 있다. 따라서 데이터를 가져온 후, collectionView를 reload시켜줄 작업을 추가하면 좋다.

class ViewController: UIViewController{
    private var collectionView: UICollectionView!
    let imageManager = PhotoManager()
    
    override func viewDidLoad(){
        super.viewDidLoad()
        collectionViewConfigure()
        collectionViewDelegate()
        
        imageManager.authorization {
            DispatchQueue.main.async {
                self.collectionView.reloadData()
            }
        }
    }
}
final class PhotoManager: PHCachingImageManager{
    private var fetchResult: PHFetchResult<PHAsset>?
    private(set) var thumbnailSize = CGSize(width: 100, height: 100)
    
    func fetchImage(){
        self.fetchResult = PHAsset.fetchAssets(with: nil)
    }
    
    func requestImage(asset: PHAsset?, thumbnailSize: CGSize, completion handler: @escaping (UIImage?) -> Void){
        guard let asset = asset else {
            let noImage = UIImage(systemName: "multiply")
            handler(noImage)
            return
        }

        self.requestImage(for: asset, targetSize: thumbnailSize, contentMode: .aspectFit, options: nil){ image, _ in
            handler(image)
        }
    }
    
    func authorization(completion: @escaping () -> ()){
        let status = PHPhotoLibrary.authorizationStatus()
        switch status {
        case .authorized:
            self.fetchImage()
            
        default:
            PHPhotoLibrary.requestAuthorization(){ status in
                switch status {
                case .authorized:
                    self.fetchImage()
                    completion()
                default:
                    break
                }
            }
        }
    }
}

이외에도 caching이나 여러 옵션들을 만질 수 있지만, 해당 부분들은 직접 여러 작업을 해보면서 경험해보는 것이 좋을 듯 하여 아주 기본적인 작업만 기술하고 여기서 마치도록 한다.

profile
중2병도 iOS가 하고싶어

0개의 댓글