Document Picker swiftUI

Hello a tous. J’espère que vous allez bien.

J’avance petit à petit sur mon projet d’application. Aujourd’hui, j’ai besoin d’importer des données sous la forme de Data dans la base de données. Notamment du PDF. Je n’arrive pas à trouver un tuto qui traite de ça dans un projet SwiftUI. Si quelqu’un pouvait m’indiquer une piste ?

Voici ce que j’ai fait jusque là. Mon picker apparait bien mais je n’arrive jamais à récupérer la donnée que je dois ajouter. Dans ce que j’ai compris, je récupère l’URL du fichier par mon Picker puis, en suite, je peux ajouter cette donnée grâce à mon URL. Mais je ne récupère jamais l’URL. Le problème se situe donc du côté de mon picker :-1: Merci par avance de votre aide.

import SwiftUI
import CoreData



struct DocumentAdd: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    
    @State var fileURL: [URL] = []
    @State var showDocumentPicker = false

    let patient: Patient
    
    
    
    fileprivate func addDoc() {
        
        if Document().addDocument(moc: managedObjectContext, documentURL: fileURL[0], patient: patient) {
            print("C'est ajouté !")
        } else {
            print("ça n'a pas fonctionné")
        }
    }
    
    var body: some View {
        VStack {
            
            //J'affiche le nom du patient pour lequel j'ajoute la donnée
            Text("Ajout d'un document pour")
            Text(patient.nom ?? "Sans nom")
            
            //Je montre un bouton qui appelle le documentPicker
            Button("Add document"){
                showDocumentPicker = true
            }
        }
        .sheet(isPresented: self.$showDocumentPicker) {
            DocumentPicker(fileContent: $fileURL)
        }
    }
}

struct DocumentPicker: UIViewControllerRepresentable {
    @Binding var fileContent: [URL]
    
    func makeCoordinator() -> DocumentPickerCoordinator {
        return DocumentPickerCoordinator(fileURL: $fileContent)
    }
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<DocumentPicker>) -> UIDocumentPickerViewController {
        let controller: UIDocumentPickerViewController
        
        controller = UIDocumentPickerViewController(forOpeningContentTypes: [.pdf], asCopy: true)
        
        return controller
    }
    
    func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: UIViewControllerRepresentableContext<DocumentPicker>) {
        ///nothing
    }
}

class DocumentPickerCoordinator: NSObject, UIDocumentPickerDelegate, UINavigationControllerDelegate {
    @Binding var fileURL: [URL]
    
    init(fileURL: Binding<[URL]>) {
        _fileURL = fileURL
    }

    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        fileURL = urls
    }
    
}


struct DocumentAdd_Previews: PreviewProvider {
    static var previews: some View {
        let patient = Patient()
        
        DocumentAdd(patient: patient).environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

Hello,
J’avoue ne pas avoir compris ce que tu veux faire dans un premier temps avec ton picker. Quel est ton objectif ? récupérer le pdf et l’afficher ?

Si au delà du picker tu veux récupérer le contenu du PDF, il ne faut pas oublier que c’est du plain text. tu veux stocker le texte tel quel ? analyser le contenu, récupérer des infos et le stocker dans une base de données structurées ? L’approche est sacrément différente selon ce que tu veux faire.

J’ai implémenté UIDocumentPickerViewController en Objective-C, il faut bien récupérer l’URL (c’est une URL temporaire de type file://…)

Je vois que tu t’es donné bien du mal. Un picker, c’est quand tu as un nombre limité, fini, de données à traiter. Tu peux établir une correspondance entre une enum et la liste de tes données ? ne cherche pas à mettre tes objets contenant tes données dan le picker, passe par une enum. Évite objC, c’est ancien, compliqué, même si en fait c’est la même chose qui se passe ou à peu près. Je n’aime pas faire ça, mais il se pourrait que ça t’aide, alors voici le bout d’un code à moi, qui utilise un picker :

Picker(« depItems »,selection: $selectedItem,content: {
ForEach(depItems.allCases){depItems → Text in if (depItems.rawValue == « data_1 ») {return Text(DepName[listOfItems[6]] ?? « data_1 »).fontWeight(.bold)} else if (depItems.rawValue == « data_2 »)
{return Text(DepName[listOfItems[8]] ?? « data_2 »).fontWeight(.bold)}
else
{return Text(depItems.rawValue).fontWeight(.bold)}
}//For each
}) // content, picker

depItems est une enum, rawvalue fait d’un élément de cette enum quelque chose que tu puisses comparer à une String, ce qui te permet de renvoyer la sélection choisie par l’utilisateur avec ton picker.
Les données réelles restent là où elles sont, le picker permet seulement de demander son choix à l’utilisateur final.
Si tes données, et donc tes choix sont trop nombreux ou en nombre variable, préfère passer par une liste, ce sera plus simple à manipuler ?

1 J'aime

Merci pour ta réponse, c’est en effet ce que j’ai compris, je travaille à partir de l’URL pour récupérer la sélection de l’utilisateur et à partir de ça, je peux importer mes données. Le soucis, c’est que dans le lien de la doc que tu m’as envoyé, il n’y a aucun exemple de code. Peut-Être en aurais-tu un ? :blush:

je ne m’en sors pas :-/ est-ce que ton exemple renvoie bien une URL temporaire de type file://… qui indique là où se trouve le fichier à importer choisi par l’utilisateur ?

Hello,

Si ton documentPicker est bien implementé si tu mets un breakPoint dans cette fonction

func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
       fileURL = urls

}

Le fait de sélectionner un document dans le documentPicker doit déclencher le breakPoint.
Ensuite tu peux voir l’url du fichier en tapant dans le debugger

po urls.first

Voila mon implémentation pour exemple:

func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
    _pickedDocument = urls.first
    guard let document = _pickedDocument else {presentError("No document found")}
    if String(describing: document).hasSuffix("pdf") {
        _documentExtension = "PDF"
        _documentHasBeenAdded = true
    }

}

Ensuite tu as 2 solutions:

  • Soit sauvegarder le document pdf directement dans CoreData avec un sttribut de type binary(ca correspond a NSData en gros) et en activant sur l’attribut « Allows External Storage ».
  • Sinon (et c’est la meilleurs solution à mon avis) tu sauvegardes directement le ficher dans la sandbox de l’app et tu sauvegarde en base son url sous forme de String.

Voila une petite fonction pour sauvegarder une url

private func save(documentAtUrl url: URL, to destination: URL, completion: () -> Void) {
    do {
        try FileManager.default.copyItem(at: url, to: destination)
        completion()
    }catch {
       presentError("Connot create file -> \(error.localizedDescription)")
    }

}

Le premier argument correspond à l’url que tu as récupérée depuis le document Picker
Le second c’est à toi de construire l’url pour dire au FileManager où tu veux copier le document.
Je t’invites à regarder du coté de FileManager pour faire ça ce n’est pas très compliqué :slightly_smiling_face:

Seb

Bon, je n’y suis pas directement arrivé avec ton code, mais ça m’a mis sur une autre voie !!! :blush:

Je te partage volontiers ma découverte :bulb:: .fileImporter

Tu lui donnes une variable Binding déclarée avec State var, tu te mets un bouton qui toggle cette variable

Button("Open", action: {

openFile.toggle()

})

Tu appelles .fileImporter (moi je l’ai mis à la closure de ma Vstack) et voilà :slight_smile:

Soit tu mets un .onChange pour appeler l fonction qui enregistre tes données, soit tu as un autre bouton.

.fileImporter(isPresented: $openFile, allowedContentTypes: [.pdf]) { (res) in

do {

fileURL = try res.get()

print(fileURL ?? "")

fileName = fileURL!.lastPathComponent

} catch {

print("error reading docs")

print(error.localizedDescription)

}

}
1 J'aime

Top.
Effectivement j’ai suivi la logique UIKit je ne connais pas très bien SwiftUI encore.
Merci pour l’info je la garde dans un petit signet :slightly_smiling_face:

Seb