Como encontrar o NSDocumentDirectory no Swift?

162

Estou tentando encontrar o caminho para a pasta Documentos com o código:

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

mas o Xcode dá erro: Cannot convert expression's type 'AnyObject[]!' to type 'NSSearchPathDirectory'

Estou tentando entender o que há de errado no código.

Ivan R
fonte
1
Houve várias edições nesta pergunta que estavam adicionando possíveis soluções. A coisa toda estava uma bagunça. Voltei à primeira versão para maior clareza. As respostas não pertencem a uma pergunta e devem ser postadas como respostas. Estou disponível para discutir se alguém acha que minha reversão é muito radical. Obrigado.
Eric Aya

Respostas:

254

Aparentemente, o compilador pensa que NSSearchPathDirectory:0é uma matriz e, é claro, espera o tipo NSSearchPathDirectory. Certamente não é uma mensagem de erro útil.

Mas quanto aos motivos:

Primeiro, você está confundindo os nomes e tipos de argumentos. Dê uma olhada na definição de função:

func NSSearchPathForDirectoriesInDomains(
    directory: NSSearchPathDirectory,
    domainMask: NSSearchPathDomainMask,
    expandTilde: Bool) -> AnyObject[]!
  • directorye domainMasksão os nomes, você está usando os tipos, mas deve deixá-los de fora para as funções de qualquer maneira. Eles são usados ​​principalmente em métodos.
  • Além disso, Swift é fortemente digitado, portanto você não deve usar 0. Use o valor da enumeração.
  • E, finalmente, ele retorna uma matriz, não apenas um único caminho.

Isso nos deixa com (atualizado para o Swift 2.0):

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

e para Swift 3:

let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
nschum
fonte
Esta resposta falhou no Xcode 6.0. O elenco deve ser em NSStringvez de String.
Daniel T.
1
@DanielT. Tentei novamente e Stringfunciona no Xcode 6.0 e 6.1. Geralmente, Stringe NSStringsão ponte automaticamente no Swift. Talvez você tenha que escolher NSStringpor outro motivo.
Nschum 19/10/2014
Você tentou em um playground ou em um aplicativo real? Os resultados são diferentes. O código é transmitido para a Stringem um playground, mas não em um aplicativo. Confira questão ( stackoverflow.com/questions/26450003/... )
Daniel T.
Ambos, na verdade. Poderia ser um bug sutil no Swift, suponho ... Vou editar a resposta para estar seguro. :) Obrigado
nschum
3
rodando mais uma vez a aplicação gera um caminho diferente, por ?: (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

Obter diretamente o primeiro elemento de uma matriz potencialmente causará exceção se o caminho não for encontrado. Então ligar firste desembrulhar é a melhor solução

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
fonte
32

A recomendação moderna é usar NSURLs para arquivos e diretórios em vez de caminhos baseados em NSString:

insira a descrição da imagem aqui

Portanto, para obter o diretório de documentos do aplicativo como um 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
}

Isso tem tratamento rudimentar de erros, pois esse tipo de depende do que seu aplicativo fará nesses casos. Mas isso usa URLs de arquivo e uma API mais moderna para retornar a URL do banco de dados, copiando a versão inicial do pacote se ele ainda não existir, ou nulo em caso de erro.

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

Normalmente eu prefiro usar esta extensão:

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]
    }
}
Alessandro Ornano
fonte
onde chamar isso?
B.Saravana Kumar
Para todas as partes do seu código que você precisa: let path = FileManager.documentsDir()+("/"+"\(fileName)")você pode chamá-lo sem diferenças entre os threads (principal ou em segundo plano).
Alessandro Ornano
14

Para quem olha o exemplo que trabalha com o Swift 2.2, o código Abizern com o moderno tenta pegar o erro

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
}

Atualização Perdi que o novo swift 2.0 tem proteção (Ruby, a menos que seja analógico), portanto, com a proteção, é muito mais curto e mais legível

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

Método Swift 3 mais conveniente :

let documentsUrl = FileManager.default.urls(for: .documentDirectory, 
                                             in: .userDomainMask).first!
Andrey Gordeev
fonte
1
Ou até mesmoFileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
Iulian Onofrei 23/11
7
Não acho que seja necessário instanciar um novo gerenciador de arquivos toda vez que queremos obter um URL para o diretório Documents.
Andrey Gordeev
1
Eu pensei que é a mesma coisa. Obrigado!
Iulian Onofrei 23/11
5

Xcode 8b4 Swift 3.0

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

Normalmente, prefiro o seguinte no swift 3 , porque posso adicionar um nome de arquivo e criar um arquivo facilmente

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
fonte
1
absoluteStringestá errado. para obter o caminho do arquivo fro um fileURL você precisa para obter a sua .pathpropriedade
Leo Dabus