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