Meister619
@Meister619 (Loïc LE TEXIER)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

SwiftUI 2.0:「[DocumentManager] Failed to associate thumbnails for picked URL」エラー

解決したいこと

UIDocumentPickerViewControllerを利用してiPhoneまたはiCloudからアプリに録音のファイルをインポートしたいですが、
作成したファイルピッカーがサムネイルエラーを発生させます。

発生している問題・エラー

2021-04-01 09:30:00.000000+0900 MyApp[4096:1234567] 
[DocumentManager] Failed to associate thumbnails for picked URL 
    file:///private/var/mobile/Containers/Shared/AppGroup/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/File%20Provider%20Storage/Voice/TestFile.m4a 
with the Inbox copy 
    file:///private/var/mobile/Containers/Data/Application/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/tmp/com.m-gild.MyApp-Inbox/TestFile.m4a: 
Error 
    Domain=QLThumbnailErrorDomain 
    Code=102 "(null)" 
    UserInfo={
        NSUnderlyingError=0x2806a2df0 {
            Error Domain=GSLibraryErrorDomain Code=3 "Generation not found" UserInfo={
                NSDescription=Generation not found
            }
        }
    }

自分で試したこと

この問題を克服するためにQLThumbnailGeneratorでサムネイルを作ろうとしたけど上手くいっかなかったから、
どなたか助ければ嬉しいです。

該当するソースコード

録音ファイルピッカー:

AudioFilePicker.swift
import SwiftUI
import UniformTypeIdentifiers
import QuickLookThumbnailing

/// 録音ファイルを投稿に添付するためのピッカーを定義するストラクトです。
struct AudioFilePicker: UIViewControllerRepresentable {
    /// ユーザーがインポートするために選択できる `UTType`ファイルデータタイプのリスト:` audio`録音ファイル。
    let documentTypes = [UTType.audio]
    /// ユーザーが選択したファイルが追加される添付ファイルのリスト。
    @Binding var selectedFiles: Array<AudioFileObject>
    /// この`PresentationMode`環境変数は、この` View`が `ParentView`によって提示されることを示す。ピッカーを閉じるために使用される。
    @Environment(\.presentationMode) private var presentationMode
    /// `UIDocumentPickerViewController`と対話して選択されたファイルを取得するオブジェクトを定義するクラスである。` UIDocumentPickerDelegate`のデリゲートを実装する必要がある。`Coordinator`は、コントローラーのデリゲートとSwiftUIの間のブリッジとして機能する。
    final class Coordinator: NSObject, UIDocumentPickerDelegate {
        /// 親録音ファイルピッカーです。<br>
        var parent: AudioFilePicker
        /// 初期化でこのコーディネーターオブジェクトの親を設定する。
        init(_ parent: AudioFilePicker) {
            self.parent = parent
        }
        /// このデリゲートメソッドは、ユーザーが1つ以上のドキュメントを選択したときに呼び出される。
        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
            urls.forEach { audioURL in
                /// セキュリティスコープのリソースへのアクセスを開始する。
                if audioURL.startAccessingSecurityScopedResource() {
                    do {
                        let request = QLThumbnailGenerator.Request(fileAt: audioURL,
                                                                   size: CGSize(width: 200, height: 200),
                                                                   scale: UIScreen.main.scale,
                                                                   representationTypes: QLThumbnailGenerator.Request.RepresentationTypes.thumbnail
                        )
                        QLThumbnailGenerator.shared.generateRepresentations(for: request) { (thumbnail, type, error) in
                            DispatchQueue.main.async(execute: {
                                                        if thumbnail == nil || error != nil {
                                                            assert(false, "Thumbnail failed to generate")
                                                        } else {
                                                            let image = thumbnail!.cgImage
                                                        }
                                self.parent.selectedFiles.append(AudioFileObject(id: UUID().uuidString, url: audioURL))
                            })
                        }
                    }
                    catch {
                        print("Error \(error)")
                    }
                }
            }
            parent.presentationMode.wrappedValue.dismiss()
        }
        /// このメソッドは、ユーザーがドキュメントピッカーをキャンセルしたことをデリゲートに通知する。
        func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
            parent.presentationMode.wrappedValue.dismiss()
        }
    }
    /// このメソッドは、 `AudioFilePicker`に実装し、` UIDocumentPickerDelegate`プロトコルを採用するために `Coordinator`インスタンスを提供する必要がある。
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    /// `UIViewControllerRepresentable`プロトコルで必要であるメソッドです。録音ファイルピッカーが初期化されるときに呼び出される。 `UIDocumentPickerDelegate`をインスタンス化して初期状態を構成しなければならない。
    func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
        let controller = UIDocumentPickerViewController(forOpeningContentTypes: self.documentTypes, asCopy: true)
        controller.allowsMultipleSelection = false
        controller.delegate = context.coordinator
        return controller
    }
    ///`UIViewControllerRepresentable`プロトコルで必要であるメソッドです。アプリの状態が変化したときに呼び出され、録音ファイルピッカーの構成に影響を与える。更新するものがないため、メソッドは空白のままにする。
    func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext<AudioFilePicker>) {
    }
}

ParentViewの中のボタンを謳歌すると録音ファイルピッカーが表示される:

ParentView.swift
struct ParentView: View {
    /// 編集画面の状態情報
    @ObservedObject private var vm = ParentViewModel()
    /// `isShowingFileImportPicker`_フラグ_が*true*の場合はオーディオレコーダーのポップアップが表示され、または*false*の場合は非表示になる
    @State private var isShowingFileImportPicker = false
    /// 既存の投稿のコンストラクタです。
    init(audioFiles: Array<AudioFileObject>) {
        vm.audioFiles =  audioFiles
    }
    
    var body: some View {
        VStack {
            Button(action: {
                self.isShowingFileImportPicker = true
            }) {
                Image(systemName: "waveform.circle.fill")
                    .font(.system(size: 40))
            }.sheet(isPresented: self.$isShowingFileImportPicker) {
                AudioFilePicker(selectedFiles: Binding(get: {vm.audioFiles}, set: { vm.audioFiles = $0 }))
            }
        }
    }
}
1

1Answer

Your answer might help someone💌