Comment trouver NSDocumentDirectory dans Swift?

162

J'essaie d'obtenir le chemin du dossier Documents avec le code:

var documentsPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory:0,NSSearchPathDomainMask:0,true)

mais Xcode donne une erreur: Cannot convert expression's type 'AnyObject[]!' to type 'NSSearchPathDirectory'

J'essaie de comprendre ce qui ne va pas dans le code.

Ivan R
la source
1
Il y a eu plusieurs modifications à cette question qui ajoutaient des solutions possibles. Le tout était en désordre. Je suis revenu à la première version pour plus de clarté. Les réponses n'appartiennent pas à une question et doivent être affichées en tant que réponses. Je suis disponible pour discuter si quelqu'un pense que mon retour en arrière est trop radical. Merci.
Eric Aya

Réponses:

256

Apparemment, le compilateur pense NSSearchPathDirectory:0 c'est un tableau, et bien sûr il attend le type à la NSSearchPathDirectoryplace. Certainement pas un message d'erreur utile.

Mais quant aux raisons:

Tout d'abord, vous confondez les noms et les types d'argument. Jetez un œil à la définition de la fonction:

func NSSearchPathForDirectoriesInDomains(
    directory: NSSearchPathDirectory,
    domainMask: NSSearchPathDomainMask,
    expandTilde: Bool) -> AnyObject[]!
  • directory et domainMask sont les noms, vous utilisez les types, mais vous devriez quand même les laisser de côté pour les fonctions. Ils sont principalement utilisés dans les méthodes.
  • De plus, Swift est fortement typé, vous ne devriez donc pas simplement utiliser 0. Utilisez plutôt la valeur de l'énumération.
  • Et enfin, il renvoie un tableau, pas seulement un seul chemin.

Cela nous laisse donc (mis à jour pour Swift 2.0):

let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]

et pour Swift 3:

let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
nschum
la source
Cette réponse a échoué dans Xcode 6.0. Le casting doit être vers NSStringplutôt que String.
Daniel T.
1
@DanielT. Je viens de réessayer et Stringfonctionne sous Xcode 6.0 et 6.1. Généralement, Stringet NSStringsont pontés automatiquement dans Swift. Peut-être avez-vous dû vous lancer NSStringpour une autre raison.
nschum
L'avez-vous essayé dans une aire de jeux ou dans une application réelle? Les résultats sont différents. Le code est converti en un Stringdans une aire de jeux, mais pas dans une application. Consultez la question ( stackoverflow.com/questions/26450003/… )
Daniel T.
Les deux, en fait. Cela pourrait être un bug subtil dans Swift, je suppose ... Je vais simplement modifier la réponse pour être sûr. :) Merci
nschum
3
exécuter à nouveau l'application génère un chemin différent, pourquoi?: (1) /var/mobile/Containers/Data/Application/9E18A573-6429-434D-9B42-08642B643970/Documents(2)/var/mobile/Containers/Data/Application/77C8EA95-B77A-474D-8522-1F24F854A291/Documents
János
40

Swift 3.0 et 4.0

Obtenir directement le premier élément d'un tableau entraînera potentiellement une exception si le chemin n'est pas trouvé. Donc, appeler firstpuis déballer est la meilleure solution

if let documentsPathString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
    //This gives you the string formed path
}

if let documentsPathURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
    //This gives you the URL of the path
}
Fangming
la source
32

La recommandation moderne est d'utiliser les NSURL pour les fichiers et les répertoires au lieu des chemins basés sur NSString:

entrez la description de l'image ici

Donc, pour obtenir le répertoire Document de l'application en tant que NSURL:

func databaseURL() -> NSURL? {

    let fileManager = NSFileManager.defaultManager()

    let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)

    if let documentDirectory: NSURL = urls.first as? NSURL {
        // This is where the database should be in the documents directory
        let finalDatabaseURL = documentDirectory.URLByAppendingPathComponent("items.db")

        if finalDatabaseURL.checkResourceIsReachableAndReturnError(nil) {
            // The file already exists, so just return the URL
            return finalDatabaseURL
        } else {
            // Copy the initial file from the application bundle to the documents directory
            if let bundleURL = NSBundle.mainBundle().URLForResource("items", withExtension: "db") {
                let success = fileManager.copyItemAtURL(bundleURL, toURL: finalDatabaseURL, error: nil)
                if success {
                    return finalDatabaseURL
                } else {
                    println("Couldn't copy file to final location!")
                }
            } else {
                println("Couldn't find initial database in the bundle!")
            }
        }
    } else {
        println("Couldn't get documents directory!")
    }

    return nil
}

Cela a une gestion des erreurs rudimentaire, car cela dépend de ce que votre application fera dans de tels cas. Mais cela utilise des URL de fichiers et une API plus moderne pour renvoyer l'URL de la base de données, en copiant la version initiale hors du bundle si elle n'existe pas déjà, ou une valeur nulle en cas d'erreur.

Abizern
la source
24

Xcode 8.2.1 • Swift 3.0.2

let documentDirectoryURL =  try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)

Xcode 7.1.1 • Swift 2.1

let documentDirectoryURL =  try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)
Leo Dabus
la source
21

Habituellement, je préfère utiliser cette extension:

Swift 3.x et Swift 4.0 :

extension FileManager {
    class func documentsDir() -> String {
        var paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as [String]
        return paths[0]
    }

    class func cachesDir() -> String {
        var paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true) as [String]
        return paths[0]
    }
}

Swift 2.x :

extension NSFileManager {
    class func documentsDir() -> String {
        var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as [String]
        return paths[0]
    }

    class func cachesDir() -> String {
        var paths = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true) as [String]
        return paths[0]
    }
}
Alessandro Ornano
la source
où appeler ça?
B.Saravana Kumar
Pour chaque partie de votre code dont vous avez besoin:, let path = FileManager.documentsDir()+("/"+"\(fileName)")vous pouvez l'appeler sans différences entre les threads (principal ou arrière-plan).
Alessandro Ornano
14

Pour tous ceux qui recherchent un exemple qui fonctionne avec Swift 2.2, le code Abizern avec moderne essaie de saisir la poignée d'erreur

func databaseURL() -> NSURL? {

    let fileManager = NSFileManager.defaultManager()

    let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)

    if let documentDirectory:NSURL = urls.first { // No use of as? NSURL because let urls returns array of NSURL
        // This is where the database should be in the documents directory
        let finalDatabaseURL = documentDirectory.URLByAppendingPathComponent("OurFile.plist")

        if finalDatabaseURL.checkResourceIsReachableAndReturnError(nil) {
            // The file already exists, so just return the URL
            return finalDatabaseURL
        } else {
            // Copy the initial file from the application bundle to the documents directory
            if let bundleURL = NSBundle.mainBundle().URLForResource("OurFile", withExtension: "plist") {

                do {
                    try fileManager.copyItemAtURL(bundleURL, toURL: finalDatabaseURL)
                } catch let error as NSError  {// Handle the error
                    print("Couldn't copy file to final location! Error:\(error.localisedDescription)")
                }

            } else {
                print("Couldn't find initial database in the bundle!")
            }
        }
    } else {
        print("Couldn't get documents directory!")
    }

    return nil
}

Mise à jour J'ai manqué que le nouveau swift 2.0 a une garde (Ruby sauf analogique), donc avec la garde, elle est beaucoup plus courte et plus lisible

func databaseURL() -> NSURL? {

let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)

// If array of path is empty the document folder not found
guard urls.count != 0 else {
    return nil
}

let finalDatabaseURL = urls.first!.URLByAppendingPathComponent("OurFile.plist")
// Check if file reachable, and if reacheble just return path
guard finalDatabaseURL.checkResourceIsReachableAndReturnError(nil) else {
    // Check if file is exists in bundle folder
    if let bundleURL = NSBundle.mainBundle().URLForResource("OurFile", withExtension: "plist") {
        // if exist we will copy it
        do {
            try fileManager.copyItemAtURL(bundleURL, toURL: finalDatabaseURL)
        } catch let error as NSError { // Handle the error
            print("File copy failed! Error:\(error.localizedDescription)")
        }
    } else {
        print("Our file not exist in bundle folder")
        return nil
    }
    return finalDatabaseURL
}
return finalDatabaseURL 
}
Roman Safin
la source
13

Méthode Swift 3 plus pratique :

let documentsUrl = FileManager.default.urls(for: .documentDirectory, 
                                             in: .userDomainMask).first!
Andrey Gordeev
la source
1
Ou mêmeFileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
Iulian Onofrei
7
Je ne pense pas qu'il soit nécessaire d'instancier un nouveau gestionnaire de fichiers chaque fois que nous voulons obtenir une URL pour le répertoire Documents.
Andrey Gordeev
1
Je pensais que c'était la même chose. Merci!
Iulian Onofrei
5

Xcode 8b4 Swift 3.0

let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
Siavash Alp
la source
3

Habituellement, je préfère comme ci-dessous dans swift 3 , car je peux ajouter un nom de fichier et créer un fichier facilement

let fileManager = FileManager.default
if let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
    let databasePath = documentsURL.appendingPathComponent("db.sqlite3").path
    print("directory path:", documentsURL.path)
    print("database path:", databasePath)
    if !fileManager.fileExists(atPath: databasePath) {
        fileManager.createFile(atPath: databasePath, contents: nil, attributes: nil)
    }
}
do01
la source
1
absoluteStringest faux. pour obtenir le chemin du fichier d'une fileURL, vous devez obtenir sa .pathpropriété
Leo Dabus