본문 바로가기

삶은계란 (Diary)/Xcode

PHPickerViewController가 특정 이미지를 불러오지 못하는 문제

서비스 중인 JusTheme의 업데이트를 준비하던 도중 작업에 필요한 유틸리티 앱을 만들었다.
스토어 등록을 위해 마무리 준비를 하던 도중 특정 이미지를 불러오지 못하는 문제를 발견해서 이를 수정했다.

결론부터 말하자면 PHPicker는 현재 애플에서 제공하는 가이드라인 대로 작업하면 WebP를 제대로 표시할 수 없다.
애플이 권장하는 PHPicker의 구현 방식은 다음과 같다.

 

Meet the new Photos picker - WWDC20 - Videos - Apple Developer

Let people select photos and videos to use in your app without requiring full Photo Library access. Discover how the PHPicker API for iOS...

developer.apple.com

if itemProvider.canLoadObject(ofClass: UIImage.self) {

    // Handle UIImage type
    itemProvider.loadObject(ofClass: UIImage.self) { image, error in

        guard let resultImage = image as? UIImage else {
            return
        }

        // Do something with `resultImage`
    }
}

굉장히 간단하지만 문제는 loadObject 메서드다.
애플은 iOS14부터 WebP 형식의 이미지를 다각도로 지원하고 있지만, 어째서인지 loadObject 메서드는 WebP를 지원하지 않고 있다.
만약 전달된 이미지가 WebP라면 해당 메서드는 False를 반환하고, 결과적으로 if문 조건에 해당하는 canLoadObject 메서드를 만족시키지 못하게 된다.

여러 방법이 있겠지만 큰 틀을 뒤집지 않고 해결하는 방법은 위의 동작 자체가 WebP를 지원하도록 구현하면 된다.

import UIKit
import UniformTypeIdentifiers

extension NSItemProvider {
	
	enum NSItemProviderLoadImageError: Error {
		case unexpectedImageType
	}
	
	func loadImage(completion: @escaping (UIImage?, Error?) -> Void) {
		
		if canLoadObject(ofClass: UIImage.self) {
			
			// Handle UIImage type
			loadObject(ofClass: UIImage.self) { image, error in
			   
				guard let resultImage = image as? UIImage else {
					completion(nil, error)
					return
				}
				
				completion(resultImage, error)
			}
			
		} else if hasItemConformingToTypeIdentifier(UTType.webP.identifier) {
			
			// Handle WebP Image
			loadDataRepresentation(forTypeIdentifier: UTType.webP.identifier) { data, error in
				
				guard let data,
					  let webpImage = UIImage(data: data) else {
					completion(nil, error)
					return
				}
				
				completion(webpImage, error)
			}
			
		} else {
			completion(nil, NSItemProviderLoadImageError.unexpectedImageType)
		}
	}
}

NSItemProvider에 해소운 메서드를 추가해 줬다.
새로운 loadImage 메서드는 애플의 구현 가이드를 제대로 만족하면서, loadObject가 False를 반환했을 때 WebP 처리를 추가한 메서드이다.

class Coordinator: NSObject, PHPickerViewControllerDelegate {
    let parent: ImagePicker

    init(_ parent: ImagePicker) {
        self.parent = parent
    }

    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        picker.dismiss(animated: true)

        guard let provider = results.first?.itemProvider else { return }

        if provider.canLoadObject(ofClass: UIImage.self) {
            provider.loadObject(ofClass: UIImage.self) { image, _ in
                self.parent.image = image as? UIImage
            }
        }
    }
}

위와 같이 가이드에 맞도록 구현했을 때, if문 자체를 통으로 해당 메서드로 대체하면 된다.

class Coordinator: NSObject, PHPickerViewControllerDelegate {
    let parent: ImagePicker

    init(_ parent: ImagePicker) {
        self.parent = parent
    }

    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        picker.dismiss(animated: true)

        guard let provider = results.first?.itemProvider else { return }

        provider.loadImage { image, error in
            self.parent.image = image
        }
    }
}

그럼 위와 같이 수정할 수 있고, WebP 이미지도 멀쩡히 사용할 수 있다.