← Retour

Importer un fichier sur iOS avec Swift et UIKit

~5 min

On va voir comment importer un fichier sur iOS avec Swift et UIKit !

Si les tutoriels sur les cas d’usage simples sont nombreux, rares sont ceux qui couvrent les subtilitĂ©s liĂ©es Ă  l’import de fichiers sur iOS. Avec UIKit, et notamment sur les anciennes versions d’iOS (iOS 12+), on peut vite se retrouver limitĂ© ou perdu.

Intro, la base, récupérer un fichier

1. Déclarer le picker et utiliser la delegation

Dans votre ViewController, déclarez un UIDocumentPickerViewController et conformez votre ViewController au protocole UIDocumentPickerDelegate.

import UIKit

class ViewController: UIViewController {

    var documentPicker: UIDocumentPickerViewController!

    override func viewDidLoad() {
        super.viewDidLoad()

        if #available(iOS 14.0, *) {
            documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: importableFileTypes)
        } else {
            documentPicker = UIDocumentPickerViewController(documentTypes: getLegacyImportableFileType(), in: .open)
        }

        documentPicker.delegate = self
        documentPicker.allowsMultipleSelection = false
        present(documentPicker, animated: true)
    }
}

Ce code devrait vous permettre d’ouvrir un document picker pour importer un fichier sur iOS.

2. Exploiter les URLs retournées par le systÚme


func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
    urls.forEach { url in // Ici, on aura en général un seul fichier, mais on peut en avoir plusieurs

    guard url.startAccessingSecurityScopedResource() else { return }

    defer { url.stopAccessingSecurityScopedResource() }

    do {
        try handleSelectedFileFromPicker(importedFileUrl: url)
    } catch {
       // Handle error
    }
}

3. Récupérer les informations du fichier

Ici on va récupérer les informations du fichier, et le stocker dans un dossier temporaire. Les informations récupérées sont :

  • Le nom du fichier
  • La taille du fichier
  • La date de derniĂšre modification

On peut aussi rĂ©cupĂ©rer le type de fichier (UTType), le MIME type, etc. L’API URLResourceValues est suffisamment riche pour couvrir la majoritĂ© des besoins..

// handleSelectedFileFromPicker(importedFileUrl: URL) throws

let resourceValues: URLResourceValues
let mimeType: String?
let fileType: String?
var resourceKeys: Set<URLResourceKey> = [.fileSizeKey, .nameKey, .creationDateKey, .contentModificationDateKey]

// Si on est sur iOS 14 ou plus, on peut récupérer le MIME type
resourceKeys.insert(.contentTypeKey)
resourceValues = try url.resourceValues(forKeys: resourceKeys)
guard let contentType = resourceValues.contentType else { throw InvalidMimeType }
mimeType = contentType.preferredMIMEType
fileType = contentType.localizedDescription

//
guard let fileSize = resourceValues.fileSize else { throw InvalidSizeOrNull() }
guard let fileName = resourceValues.name else { throw InvalidName() }

let lastModificationDate = resourceValues.contentModificationDate ?? resourceValues.creationDate

let data = try Data(contentsOf: url, options: .mappedIfSafe)

// On peut stocker le fichier dans un dossier temporaire pour une utilisation ultérieure
let tempUrl = data.writeToTempDirectory(fileName: fileName, mimeType: MimeType.get(from: mimeType))

Les subtilités

Les versions d’iOS

UTType est disponible Ă  partir d’iOS 14, et il est recommandĂ© de l’utiliser pour dĂ©terminer le type de fichier.

Dans le code précédent on a utilisé importableFileTypes qui est un tableau de UTType pour déterminer les types de fichiers importables.


@available(iOS 14.0, *)
var importableFileTypes: [UTType] {
    [
        .image,
        .video,
        .audio,
        .text,
        .presentation,
        .spreadsheet,
        .epub
        .delimitedText
    ]
}

Attention : .presentation ou .spreadsheet ne suffisent pas Ă  autoriser les fichiers .pptx ou .xlsx.

Il faut créer manuellement les UTType correspondants, car ils ne sont pas fournis par défaut par Apple.

Le legacy < iOS 14.0

Pour la science le legacyImportableFileType est une fonction qui retourne un tableau de String utilisant les kUTType de l’API CoreFoundation.

@available(iOS, deprecated: 14.0, message: "To be removed starting iOS 14")
func getLegacyImportableFileType() -> [String] {
        return [
            String(kUTTypePDF),
            String(kUTTypeAudio),
            String(kUTTypeVideo),
            String(kUTTypeHTML),
            String(kUTTypeSpreadsheet),
            String(kUTTypeText),
            String(kUTTypePlainText),
            String(kUTTypePresentation),
            String(kUTTypeImage)
        ]
}

Attention Ă  startAccessingSecurityScopedResource()

Il est important de bien appeler startAccessingSecurityScopedResource() sur l’URL retournĂ© par le document picker, pour pouvoir accĂ©der au contenu du fichier.

Attention à ne pas oublier de bien appeler stopAccessingSecurityScopedResource() pour libérer les ressources allouées par le systÚme.

Mais si vous le faites trop tît, vous n’aurez pas accùs au contenu du fichier.

L’extension est reine

On pourrait croire que les OS rĂ©cents vĂ©rifient d’une maniĂšre moins triviale les types des fichiers, mais en fait ils ne vĂ©rifient pas le contenu du fichier pour dĂ©terminer le type. MĂȘme pas une en-tĂȘte de fichier, ou un checksum.

Ils utilisent l’extension du fichier pour dĂ©terminer le type (.jpeg, .jpg, .png, .docx, .pdf, etc
) Et cotĂ© web le standard c’est le mimeType donc image/jpeg ou image/jpg

Sur les systĂšmes d’Apple, ils utilisent les UTType pour dĂ©terminer le type de fichier, et ils ont une liste de type de fichier dĂ©jĂ  dĂ©clarĂ© par Apple, mais on peut aussi en dĂ©clarer nous mĂȘme, et mĂȘme les Ă©tendre.

UTType pour “Uniform Type Identifier” qui possĂšde plusieurs niveaux d’abstration pour reprĂ©senter un type de fichier :

Exemple :

  • Un fichier file.jpeg est un type d’image,que l’on pourrait noter file.image avec UTType, utile quand on veut importer un fichier image, on peut importer n’importe quel type d’image.
  • Mais c’est aussi un fichier “binaire”, donc file.data est aussi valide en terme d’abstraction, enfin c’est un fichier JPEG, donc file.jpeg est aussi valide.

On peut donc utiliser les UTType pour dĂ©terminer le type de fichier, et donc le type de fichier que l’on veut importer. Ils ont dĂ©jĂ  une liste de type dĂ©clarĂ© par Apple, mais on peut aussi en dĂ©clarer nous mĂȘme, et mĂȘme les Ă©tendre.

Utiliser les documents Office Ă  partir de 2007

Une plaie Ă  faire ? Oui.

Il n’existe quasiment aucune documentation officielle, ni chez Apple ni chez Microsoft, sur l’import de fichiers .docx, .pptx ou .xlsx dans une app iOS.

docx: org.openxmlformats.wordprocessingml.document
pptx: org.openxmlformats.presentationml.presentation
xlsx: org.openxmlformats.spreadsheetml.sheet

A bien mettre dans XCode dans les UTType importable.

Puis dans votre code rajouter les types comme types importable comme ceci :


import Foundation
import UniformTypeIdentifiers

@available(iOS 14.0, *)
extension UTType {
    public static var word: UTType {
        UTType(importedAs: "org.openxmlformats.wordprocessingml.document")
    }

    public static var excel: UTType {
        UTType(importedAs: "org.openxmlformats.spreadsheetml.sheet")
    }

    public static var powerpoint: UTType {
        UTType(importedAs: "org.openxmlformats.presentationml.presentation")
    }

}

Fichiers exotiques

Si vous devez gérer des fichiers exotiques, des extensions maisons, des mimeType obscurs, vous pouvez déclarer vos propres UTType. Pour cela, il suffit de déclarer un UTType comme ceci :

import Foundation
import UniformTypeIdentifiers

@available(iOS 14.0, *)
extension UTType {
    public static var myCustomType: UTType {
        UTType(importedAs: "com.mycompany.myCustomType")
    }
}

Beaucoup d’informations sur les UTType sont disponibles ici : https://developer.apple.com/videos/play/tech-talks/10696

Conclusion

Importer un fichier sur iOS avec UIKit est pas si trivial, surtout si l’on veut faire les choses proprement, de façon rĂ©tro-compatible, et en respectant les rĂšgles de sandboxing d’iOS. En comprenant bien le fonctionnement des UTType, et en gĂ©rant correctement les ressources sĂ©curisĂ©es, on peut proposer une UX robuste pour l’import de fichiers dans n’importe quelle app.