Jak znaleźć katalog NSDocumentDirectory w Swift?

162

Próbuję uzyskać ścieżkę do folderu Dokumenty z kodem:

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

ale Xcode daje błąd: Cannot convert expression's type 'AnyObject[]!' to type 'NSSearchPathDirectory'

Próbuję zrozumieć, co jest nie tak w kodzie.

Ivan R.
źródło
1
Było kilka zmian w tym pytaniu, które dodawały możliwe rozwiązania. To był bałagan. Wróciłem do pierwszej wersji dla jasności. Odpowiedzi nie należą do pytania i powinny być publikowane jako odpowiedzi. Jestem gotowy do dyskusji, jeśli ktoś uważa, że ​​moje wycofanie jest zbyt radykalne. Dzięki.
Eric Aya

Odpowiedzi:

256

Najwyraźniej kompilator uważa, że NSSearchPathDirectory:0jest tablicą i oczywiście oczekuje NSSearchPathDirectoryzamiast tego typu . Z pewnością nie jest to pomocny komunikat o błędzie.

Ale co do powodów:

Po pierwsze, mylisz nazwy i typy argumentów. Spójrz na definicję funkcji:

func NSSearchPathForDirectoriesInDomains(
    directory: NSSearchPathDirectory,
    domainMask: NSSearchPathDomainMask,
    expandTilde: Bool) -> AnyObject[]!
  • directoryi domainMasksą nazwami, używasz typów, ale i tak powinieneś je pominąć dla funkcji. Stosowane są przede wszystkim w metodach.
  • Ponadto język Swift jest silnie wpisany, więc nie powinieneś używać po prostu 0. Zamiast tego użyj wartości wyliczenia.
  • I na koniec zwraca tablicę, a nie tylko pojedynczą ścieżkę.

To pozostawia nam (zaktualizowane do Swift 2.0):

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

a dla Swift 3:

let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
nschum
źródło
Ta odpowiedź nie powiodła się w Xcode 6.0. Obsada musi być NSStringraczej do niż String.
Daniel T.
1
@DanielT. Właśnie spróbowałem ponownie i Stringdziała w Xcode 6.0 i 6.1. Ogólnie rzecz biorąc, Stringi NSStringsą zmostkowane automatycznie w Swift. Może musiałeś rzucić na NSStringz innego powodu.
nschum
Czy próbowałeś tego na placu zabaw czy w rzeczywistej aplikacji? Wyniki są różne. Kod jest rzutowany do a Stringna placu zabaw, ale nie w aplikacji. Sprawdź pytanie ( stackoverflow.com/questions/26450003/… )
Daniel T.
Właściwie obie. To może być subtelny błąd w Swift, przypuszczam ... Po prostu poprawię odpowiedź, aby była bezpieczna. :) Dzięki
nschum
3
ponowne uruchomienie aplikacji wygeneruje inną ścieżkę, dlaczego ?: (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 i 4.0

Bezpośrednie pobranie pierwszego elementu z tablicy może potencjalnie spowodować wyjątek, jeśli ścieżka nie zostanie znaleziona. Dlatego firstlepszym rozwiązaniem jest wywołanie, a następnie rozpakowanie

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
źródło
32

Nowoczesne zalecenie polega na używaniu adresów NSURL dla plików i katalogów zamiast ścieżek opartych na NSString:

wprowadź opis obrazu tutaj

Aby więc pobrać katalog dokumentów dla aplikacji jako identyfikator 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
}

Ma to podstawową obsługę błędów, ponieważ zależy to od tego, co zrobi twoja aplikacja w takich przypadkach. Ale to wykorzystuje adresy URL plików i nowocześniejszy interfejs API, aby zwrócić adres URL bazy danych, kopiując początkową wersję z pakietu, jeśli jeszcze nie istnieje, lub zerową w przypadku błędu.

Abizern
źródło
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
źródło
21

Zwykle wolę używać tego rozszerzenia:

Swift 3.x oraz 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
źródło
gdzie to nazwać?
B.Saravana Kumar
Do każdej części kodu, której potrzebujesz: let path = FileManager.documentsDir()+("/"+"\(fileName)")możesz ją wywołać bez różnic między wątkiem (głównym lub w tle).
Alessandro Ornano
14

Dla każdego, kto szuka przykładu, który działa ze Swift 2.2, kod Abizern z nowoczesnym, spróbuj złapać uchwyt błędu

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
}

Aktualizacja Brakowało mi, że nowy Swift 2.0 ma osłonę (Ruby, chyba że analog), więc z osłoną jest znacznie krótszy i bardziej czytelny

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
źródło
13

Bardziej wygodna metoda Swift 3 :

let documentsUrl = FileManager.default.urls(for: .documentDirectory, 
                                             in: .userDomainMask).first!
Andrey Gordeev
źródło
1
Lub nawetFileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
Iulian Onofrei
7
Nie sądzę, aby konieczne było tworzenie nowego menedżera plików za każdym razem, gdy chcemy uzyskać adres URL katalogu Dokumenty.
Andrey Gordeev
1
Myślałem, że to to samo. Dzięki!
Iulian Onofrei
5

Xcode 8b4 Swift 3.0.0

let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
Siavash Alp
źródło
3

Zwykle wolę jak poniżej w Swift 3 , ponieważ mogę dodać nazwę pliku i łatwo utworzyć plik

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
źródło
1
absoluteStringjest źle. aby uzyskać ścieżkę do pliku adresURL, musisz uzyskać jego .pathwłasność
Leo Dabus