¿Cómo encontrar NSDocumentDirectory en Swift?

162

Estoy tratando de obtener la ruta a la carpeta Documentos con código:

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

pero Xcode da error: Cannot convert expression's type 'AnyObject[]!' to type 'NSSearchPathDirectory'

Estoy tratando de entender lo que está mal en el código.

Ivan R
fuente
1
Hubo varias ediciones a esta pregunta que agregaban posibles soluciones. Todo fue un desastre. Regresé a la primera versión para mayor claridad. Las respuestas no pertenecen a una pregunta y deben publicarse como respuestas. Estoy disponible para discutir si alguien piensa que mi reversión es demasiado radical. Gracias.
Eric Aya

Respuestas:

254

Aparentemente, el compilador piensa que NSSearchPathDirectory:0es una matriz y, por supuesto, espera el tipo en su NSSearchPathDirectorylugar. Ciertamente no es un mensaje de error útil.

Pero en cuanto a las razones:

Primero, está confundiendo los nombres y tipos de argumentos. Eche un vistazo a la definición de la función:

func NSSearchPathForDirectoriesInDomains(
    directory: NSSearchPathDirectory,
    domainMask: NSSearchPathDomainMask,
    expandTilde: Bool) -> AnyObject[]!
  • directoryy domainMaskson los nombres, está utilizando los tipos, pero de todos modos debe dejarlos fuera para las funciones. Se utilizan principalmente en métodos.
  • Además, Swift está fuertemente tipado, por lo que no deberías usar solo 0. Usa el valor de la enumeración.
  • Y finalmente, devuelve una matriz, no solo una sola ruta.

Eso nos deja con (actualizado para Swift 2.0):

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

y para Swift 3:

let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
nschum
fuente
Esta respuesta falló en Xcode 6.0. El elenco debe ser en NSStringlugar de String.
Daniel T.
1
@DanielT. Acabo de intentarlo de nuevo y Stringfunciona en Xcode 6.0 y 6.1. Generalmente, Stringy NSStringse puentean automáticamente en Swift. Tal vez tuviste que elegir NSStringpor otra razón.
nschum
¿Lo probaste en un parque infantil o en una aplicación real? Los resultados son diferentes. El código se envía a un Stringen un patio de recreo, pero no en una aplicación. Consulta la pregunta ( stackoverflow.com/questions/26450003/… )
Daniel T.
Ambos, en realidad. Podría ser un error sutil en Swift, supongo ... Solo editaré la respuesta para estar seguro. :) Gracias
nschum
3
corriendo de nuevo, la aplicación genera una ruta diferente, ¿por qué ?: (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 y 4.0

Obtener directamente el primer elemento de una matriz puede causar una excepción si no se encuentra la ruta. Entonces, llamar firsty luego desenvolver es la mejor solución

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
}
Colmillo
fuente
32

La recomendación moderna es usar NSURL para archivos y directorios en lugar de rutas basadas en NSString:

ingrese la descripción de la imagen aquí

Entonces, para obtener el directorio de documentos para la aplicación como 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
}

Esto tiene un manejo de errores rudimentario, ya que ese tipo de depende de lo que haga su aplicación en tales casos. Pero esto usa URL de archivos y una API más moderna para devolver la URL de la base de datos, copiando la versión inicial del paquete si aún no existe, o una nula en caso de error.

Abizern
fuente
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
fuente
21

Por lo general, prefiero usar esta extensión:

Swift 3.xy 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
fuente
donde llamar a esto?
B.Saravana Kumar
Para cada parte de su código que necesite: let path = FileManager.documentsDir()+("/"+"\(fileName)")puede llamarlo sin diferencias entre el hilo (principal o de fondo).
Alessandro Ornano
14

Para todos los que buscan un ejemplo que funcione con Swift 2.2, el código de Abizern con moderno intenta detectar el error

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
}

Actualización He echado de menos que el nuevo swift 2.0 tenga protector (Ruby a menos que sea analógico), por lo que con protector es mucho más corto y más legible

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
fuente
13

Método Swift 3 más conveniente :

let documentsUrl = FileManager.default.urls(for: .documentDirectory, 
                                             in: .userDomainMask).first!
Andrey Gordeev
fuente
1
O inclusoFileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
Iulian Onofrei
77
No creo que sea necesario crear una instancia de un nuevo administrador de archivos cada vez que queremos obtener una URL para el directorio de Documentos.
Andrey Gordeev
1
Pensé que es lo mismo. ¡Gracias!
Iulian Onofrei
5

Xcode 8b4 Swift 3.0

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

Por lo general, prefiero como a continuación en Swift 3 , porque puedo agregar el nombre del archivo y crear un archivo fácilmente

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
fuente
1
absoluteStringEstá Mal. para obtener la ruta del archivo de un archivo URL necesita obtener su .pathpropiedad
Leo Dabus