카테고리 없음

PHPickerController 사진을 선택한 순서로 저장하기

ghnn 2025. 8. 16. 22:43

커뮤니티 뷰에서 사진 업로드 기능을 구현하면서,

UIImagePickerController가 iOS 18.5부터 Deprecated된다는 소식을 듣고

PHPickerViewController로 마이그레이션을 진행했다.

여러 장 선택, 보안성 강화, 안정적인 이미지 처리 등 장점이 있다고 한다.


선택한 순서대로 이미지 불러오기

PHPickerResult 배열은 항상 사용자가 선택한 순서와 같지 않을 수 있다.

따라서, 선택 순서를 보장하려면 다음 과정을 거친다:

  1. results.compactMap { $0.assetIdentifier }로 ID 배열을 추출한다.
  2. PHAsset.fetchAssets(withLocalIdentifiers: [추출한 ID 배열], options: nil)을 호출한다.
  3. 반환된 PHFetchResult를 순회해 추출한 ID배열과 PHAsset을 딕셔너리에 1:1로 담아준다.
  4. ID배열의 순서대로 PHAsset을 담은 배열을 생성한다
  5.  PHAsset을 이용해 PHImageManager로 이미지를 요청해 UIImage 배열을 만든다.

Rx로 순서 보장 이미지 불러오기

PHAsset → UIImage 변환은 비동기이므로 Single을 활용한다.

    private func loadOrderedImages(from results: [PHPickerResult]) -> Single<[UIImage]> {
        // 사진 고유 ID 추출
        let ids = results.compactMap { $0.assetIdentifier }
        guard !ids.isEmpty else { return .just([]) }
        
        // ids 배열 순서대로 PHAsset을 담은 PHFetchResult 가져오기
        let fetched = PHAsset.fetchAssets(withLocalIdentifiers: ids, options: nil)
        
        // 사용자가 선택한 ID 순서대로 PHAsset 배열 복원
        var dics: [String: PHAsset] = [:]
          fetched.enumerateObjects { asset, _, _ in
              dics[asset.localIdentifier] = asset
          }
        
        let assetsInOrder = ids.compactMap { dics[$0] }
        
        // 각 PHAsset → 이미지 Single로 변환
        let imageSingles: [Single<UIImage?>] = assetsInOrder.map { asset in
            Single<UIImage?>.create { observer in
                let option = PHImageRequestOptions()
                option.isNetworkAccessAllowed = true
                option.deliveryMode = .highQualityFormat

                PHImageManager.default().requestImageDataAndOrientation(for: asset, options: option) { data, _, _, _ in
                    observer(.success(data.flatMap { UIImage(data: $0) }))
                }
                return Disposables.create()
            }
        }
        
        return Single.zip(imageSingles)
            .map { $0.compactMap { $0 } }   // nil 제거
    }

enumerateObjects

  • PHFetchResult는 배열이 아니기 때문에 forEach 대신 enumerateObjects라는 메서드를 제공한다.
  • 콜백 인자: (asset, index, stop) / 필요 시 stop.pointee = true로 조기 종료 가능.

PHPicker 설정 옵션 정리

// preselectedAssetIdentifiers을 사용하려면 반드시 .shared()로 설정해야 한다.
var config = PHPickerConfiguration(photoLibrary: .shared())
config.filter = .images
config.selectionLimit = 5

// UI에서 선택 순서를 숫자로 표시
config.selection = .ordered

// 가능한 한 원본 그대로 가져와 트랜스코딩을 방지.
config.preferredAssetRepresentationMode = .current

// 이미 선택된 항목들을 미리 체크해 보여줄 수 있다(iOS 15+).
// 위 메서드에서 사용한 ids 배열을 다른 곳에 저장해뒀다가 그대로 넣으면 된다.
config.preselectedAssetIdentifiers = selectedAssetIdentifiers

알게 된 점

  • PHPickerResult는 순서가 보장되지 않으므로, assetIdentifier → PHAsset → UIImage 흐름으로 재정렬해야 한다.
  • PHFetchResult는 배열이 아니므로 enumerateObjects(또는 object(at:))를 사용한다.
  • preselectedAssetIdentifiers를 쓰면 selectionLimit는 총 허용 개수만 지정하면 되고, 남은 개수는 픽커가 자동 관리한다.

참고한 자료

 

[Swift/TIL #9] PHPickerViewController에 대하여

[TIL #9] 2023 / 04 / 03 ~ 2023 / 04 / 06 사진을 가져오려 하는데 iOS 14 이상부터는 UIImagePickerController 대신 PHPickerViewController를 사용하라고 하더라고요. 그래서 오늘은 PHPickerViewController에 대해서 알아보겠

ios-daniel-yang.tistory.com