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âAPIURLResourceValues
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
doncimage/jpeg
ouimage/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 noterfile.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 fichierJPEG
, doncfile.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.