文字っぽいの。

文字を書いています。写真も混ざります。

シミュレーターのPHPickerViewControllerでHEIC形式の画像を読み込めない問題の解決方法

環境

起こること

PHPickerViewControllerを利用して画像を取得する時、インターネットに転がってるコードを参考に実装していくとこんな感じになる。

func showPicker() {
    var configuration = PHPickerConfiguration()
    configuration.filter = .any(of: [.images])

    let picker = PHPickerViewController(configuration: configuration)
    picker.delegate = self
    present(picker, animated: true)
}

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

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

    let typeChecked = itemProvider.registeredTypeIdentifiers.map { itemProvider.hasItemConformingToTypeIdentifier($0) }
    guard !typeChecked.contains(false) else {
        return
    }
    guard itemProvider.canLoadObject(ofClass: UIImage.self) else {
        return
    }

    itemProvider.loadObject(ofClass: UIImage.self) { object, error in
        let image = object as? UIImage

        DispatchQueue.main.async {
            self.imageView.image = image
        }
    }
}

showPicker() を呼べばPHPickerViewControllerが表示されて、画像を1枚選択できる。その後はコードに書いてある通り UIImage に変換して UIImageView にセットしている。

このコードだとほとんど上手くいくが、シミュレーターで試してみると『ピンク(紫)の花の画像』だけうまく取得できない。

エラーとしてはこんな感じのものが出る

[claims] Upload preparation for claim CA6415EA-8DE0-45D8-A1CD-B090459A2EC7 completed with error: Error Domain=NSCocoaErrorDomain Code=260 "The file “version=1&uuid=CC95F08C-88C3-4012-9D6D-64A413D254B3&mode=current.jpeg” couldn’t be opened because there is no such file."

調査

調べてみると、この画像だけ registeredTypeIdentifiers に "public.heic" も含まれていることがわかる。

print(itemProvider.registeredTypeIdentifiers) // => ["public.jpeg", "public.heic"]

シミュレーターに元々入っている他の画像は ["public.jpeg"] なので、HEICタイプの画像かどうかが影響していそう。

試しにHEIC形式の写真をiPhoneで撮影してシミュレーターに転送してみたところ、こちらも開けなかった。

どうやらHEIC形式の画像の場合、loadObjectが利用できなさそうなので、別の方法でデータを取得する必要がありそう。

対応方法

結論としては、

  • preferredAssetRepresentationMode.current に指定する
  • loadObject ではなく loadFileRepresentation を使う

ことでHEICもJPEGも同じように取得できた。それに対応したコードは下記。

func showPicker() {
    var configuration = PHPickerConfiguration()
    configuration.filter = .any(of: [.images])
    configuration.preferredAssetRepresentationMode = .current // この設定も追加が必要

    let picker = PHPickerViewController(configuration: configuration)
    picker.delegate = self
    present(picker, animated: true)
}

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

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

    let typeChecked = itemProvider.registeredTypeIdentifiers.map { itemProvider.hasItemConformingToTypeIdentifier($0) }
    guard !typeChecked.contains(false) else {
        return
    }

    itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.image.identifier) { (url, error) in
        guard let url = url else {
            return
        }
        guard let imageData = try? Data(contentsOf: url) else {
            return
        }

        DispatchQueue.main.async {
            self.imageView.image = UIImage(data: imageData)
        }
    }
}

備考

  • 表題通り実機だと問題なく動く
  • シミュレーターでもうまく読み込めるHEICとそうでないのがある
    • 様子を見ていると mode=current.heic から mode=current.jpeg を生成しているが、これが上手く動かないことがあるっぽい