まーいけるだろ、と自作アプリが動いていたiPhone14 Pro w/ iOS16をiPhone15 Pro w/ iOS17に移行したところ、ビルドしなおしてインストールしたら普通に動いてたっぽいから油断してたら、タイトルの通りPhotosPickerを使って写真データを取り出してるところが動かなくなってた。 具体的にはこのエントリで書いてたコード。
これの、以下のようなコード。
fileprivate extension PhotosPickerItem { func pictureData() async -> Data? { var transferable: Data? = nil do { transferable = try await self.loadTransferable(type: Data.self) } catch { // log the error return nil }
これを実行するとcatch
節で以下のようなエラーが報告された。iOS16 / iPhone14では起きなかったもの*1。
CoreTransferable.TransferableSupportError error 0
で、これ、iPhone15Proのシミュレーターでも起きないんだよね。今のところ実機でしか再現できてない。なんなんでしょう。PhotosPickerの使いかたみたいなのをググると同じようなコードで直接Data
に変換している例が大量に見付かるんだけど、みなさんどうなってるんですかね?
解法?: Transferableな変換先を自分で実装する
よく(Appleのドキュメントでも)見るloadTransferable
の変換先といえばImage
なんだけど、これだとデータ本体を取り出すことができないので却下。困ったんだけど、自分でTransferableな変換先を実装するしかないかな、ということになった。Appleの以下のドキュメントにある通り。
自分の場合は対応フォーマットとしてJPEG/PNGのデータが取り出せればよかったので、それぞれJPEGの場合とPNGの場合用のTransferableなstructを作って変換を試みたところ、これでiPhone15の実機でも正常に動くようになった。コードは下に載せておく。
直接Data
を取り出すことがなぜ失敗するのかがマジで意味不明だが*2、まあ解決した上にコードも綺麗になったような気がするので良しとする。しっかし何故なんだろう、他の人のは動いてるのかな……。
import PhotosUI fileprivate enum TransferError: Error { case importJpeg case importPng case emptyJpeg case emptyPng } struct PickedJpegImage: Transferable { let data: Data static var transferRepresentation: some TransferRepresentation { DataRepresentation(importedContentType: .image) { data in guard let uiImage = UIImage(data: data) else { throw TransferError.importJpeg } guard let data = uiImage.jpegData(compressionQuality: 1.0) else { throw TransferError.emptyPng } return PickedJpegImage(data: data) } } } struct PickedPngImage: Transferable { let data: Data static var transferRepresentation: some TransferRepresentation { DataRepresentation(importedContentType: .image) { data in guard let uiImage = UIImage(data: data) else { throw TransferError.importPng } guard let data = uiImage.pngData() else { throw TransferError.emptyPng } return PickedPngImage(data: data) } } } fileprivate extension PhotosPickerItem { func pictureData() async -> Data? { do { if self.supportedContentTypes.contains(UTType.jpeg) { guard let p = try await self.loadTransferable(type: PickedJpegImage.self) else { // error log return nil } return p.data } else if self.supportedContentTypes.contains(UTType.png) { guard let p = try await self.loadTransferable(type: PickedPngImage.self) else { // error log return nil } return p.data } else { // unknown type: error log return nil } } catch { // error log } return nil } }