¿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.


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]

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
}

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.


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

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

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 
}

13

Método Swift 3 más conveniente :

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

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)

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

1
absoluteStringEstá Mal. para obtener la ruta del archivo de un archivo URL necesita obtener su .pathpropiedad
— Leo Dabus
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.