Come trovare NSDocumentDirectory in Swift?


162

Sto cercando di ottenere il percorso alla cartella Documenti con il codice:

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

ma Xcode dà errore: Cannot convert expression's type 'AnyObject[]!' to type 'NSSearchPathDirectory'

Sto cercando di capire cosa c'è di sbagliato nel codice.


1
Ci sono state diverse modifiche a questa domanda che stavano aggiungendo possibili soluzioni. L'intera faccenda è stata un disastro. Sono tornato alla prima versione per maggiore chiarezza. Le risposte non fanno parte di una domanda e devono essere pubblicate come risposte. Sono disponibile a discutere se qualcuno pensa che il mio rollback sia troppo radicale. Grazie.
Eric Aya,

Risposte:


254

Apparentemente, il compilatore pensa che NSSearchPathDirectory:0sia un array e ovviamente si aspetta NSSearchPathDirectoryinvece il tipo . Certamente non è un messaggio di errore utile.

Ma per i motivi:

Innanzitutto, stai confondendo i nomi e i tipi degli argomenti. Dai un'occhiata alla definizione della funzione:

func NSSearchPathForDirectoriesInDomains(
    directory: NSSearchPathDirectory,
    domainMask: NSSearchPathDomainMask,
    expandTilde: Bool) -> AnyObject[]!
  • directorye domainMasksono i nomi, stai usando i tipi, ma dovresti comunque lasciarli fuori per le funzioni. Sono utilizzati principalmente nei metodi.
  • Inoltre, Swift è fortemente tipizzato, quindi non dovresti semplicemente usare 0. Usa invece il valore dell'enum.
  • E infine, restituisce un array, non solo un singolo percorso.

Questo ci lascia con (aggiornato per Swift 2.0):

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

e per Swift 3:

let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]

Questa risposta non è riuscita in Xcode 6.0. Il cast deve essere NSStringpiuttosto che String.
Daniel T.

1
@DanielT. Ho appena provato di nuovo e Stringfunziona in Xcode 6.0 e 6.1. Generalmente, Stringe NSStringvengono automaticamente ponticellati in Swift. Forse dovevi lanciarti NSStringper un altro motivo.
nschum,

L'hai provato in un parco giochi o in un'app reale? I risultati sono diversi. Il codice viene trasmesso a Stringin un parco giochi, ma non in un'app. Leggi la domanda ( stackoverflow.com/questions/26450003/… )
Daniel T.

Entrambi, in realtà. Potrebbe essere un bug sottile in Swift, suppongo ... modificherò la risposta per sicurezza. :) Grazie
nschum

3
in esecuzione di nuovo l'app genera un percorso diverso, perché ?: (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 e 4.0

L'ottenimento diretto del primo elemento da un array causerà potenzialmente un'eccezione se il percorso non viene trovato. Quindi chiamare firste scartare è la soluzione migliore

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
}

32

La moderna raccomandazione è di utilizzare NSURL per file e directory anziché percorsi basati su NSString:

inserisci qui la descrizione dell'immagine

Quindi, per ottenere la directory del documento per l'app come 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
}

Questo ha una rudimentale gestione degli errori, poiché questo tipo di dipende da ciò che l'applicazione farà in questi casi. Ma questo utilizza URL di file e un'API più moderna per restituire l'URL del database, copiando la versione iniziale dal bundle se non esiste già, o nulla in caso di errore.


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)

21

Di solito preferisco usare questa estensione:

Swift 3.xe 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]
    }
}

dove chiamarlo?
B. Sararana Kumar

Per ogni parte del codice di cui hai bisogno: let path = FileManager.documentsDir()+("/"+"\(fileName)")puoi chiamarlo senza differenze tra thread (main o background).
Alessandro Ornano,

14

Per chiunque sembri un esempio che funziona con Swift 2.2, il codice Abizern con i moderni prova a cogliere l'errore

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
}

Aggiornamento Mi è mancato il fatto che i nuovi swift 2.0 abbiano guard (Ruby a meno dell'analogo), quindi con guard è molto più breve e più leggibile

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 
}

13

Metodo Swift 3 più conveniente :

let documentsUrl = FileManager.default.urls(for: .documentDirectory, 
                                             in: .userDomainMask).first!

1
O addiritturaFileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
Iulian Onofrei il

7
Non credo sia necessario creare un'istanza di un nuovo file manager ogni volta che vogliamo ottenere un URL per la directory Documents.
Andrey Gordeev,

1
Ho pensato che fosse la stessa cosa. Grazie!
Iulian Onofrei il

5

Xcode 8b4 Swift 3.0

let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)

3

Di solito preferisco come sotto in swift 3 , perché posso aggiungere il nome del file e creare facilmente un file

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)
    }
}

1
absoluteStringè sbagliato. per ottenere il percorso del file da un fileURL devi ottenere la sua .pathproprietà
Leo Dabus,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.