Wie finde ich NSDocumentDirectory in Swift?

162

Ich versuche, den Pfad zum Ordner "Dokumente" mit dem folgenden Code abzurufen:

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

aber Xcode gibt Fehler: Cannot convert expression's type 'AnyObject[]!' to type 'NSSearchPathDirectory'

Ich versuche zu verstehen, was im Code falsch ist.

Ivan R.
quelle
1
Es gab mehrere Änderungen an dieser Frage, die mögliche Lösungen hinzufügten. Das Ganze war ein Chaos. Ich habe aus Gründen der Übersichtlichkeit auf die erste Version zurückgegriffen. Antworten gehören nicht in eine Frage und sollten als Antworten veröffentlicht werden. Ich kann diskutieren, ob jemand mein Rollback für zu radikal hält. Vielen Dank.
Eric Aya

Antworten:

254

Anscheinend denkt der Compiler, es sei NSSearchPathDirectory:0ein Array, und natürlich erwartet er NSSearchPathDirectorystattdessen den Typ . Sicher keine hilfreiche Fehlermeldung.

Aber zu den Gründen:

Zunächst verwechseln Sie die Namen und Typen der Argumente. Schauen Sie sich die Funktionsdefinition an:

func NSSearchPathForDirectoriesInDomains(
    directory: NSSearchPathDirectory,
    domainMask: NSSearchPathDomainMask,
    expandTilde: Bool) -> AnyObject[]!
  • directoryund domainMasksind die Namen, verwenden Sie die Typen, aber Sie sollten sie trotzdem für Funktionen weglassen. Sie werden hauptsächlich in Methoden verwendet.
  • Außerdem ist Swift stark typisiert, sodass Sie nicht nur 0 verwenden sollten. Verwenden Sie stattdessen den Wert der Aufzählung.
  • Und schließlich wird ein Array zurückgegeben, nicht nur ein einzelner Pfad.

So bleibt uns mit (aktualisiert für Swift 2.0):

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

und für Swift 3:

let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
nschum
quelle
Diese Antwort ist in Xcode 6.0 fehlgeschlagen. Die Besetzung muss NSStringeher zu als sein String.
Daniel T.
1
@ DanielT. Ich habe es gerade noch einmal versucht und Stringfunktioniert in Xcode 6.0 und 6.1. Im Allgemeinen Stringund NSStringwerden in Swift automatisch überbrückt. Vielleicht mussten Sie NSStringaus einem anderen Grund besetzen.
Nschum
Hast du es auf einem Spielplatz oder in einer tatsächlichen App versucht? Die Ergebnisse sind unterschiedlich. Der Code wird auf einem StringSpielplatz übertragen, jedoch nicht in einer App. Check out Frage ( stackoverflow.com/questions/26450003/… )
Daniel T.
Eigentlich beides. Könnte ein subtiler Fehler in Swift sein, nehme ich an ... Ich werde die Antwort nur bearbeiten, um sicher zu gehen. :) Danke
nschum
3
/var/mobile/Containers/Data/Application/9E18A573-6429-434D-9B42-08642B643970/Documents/var/mobile/Containers/Data/Application/77C8EA95-B77A-474D-8522-1F24F854A291/Documents
János
40

Swift 3.0 und 4.0

Das direkte Abrufen des ersten Elements aus einem Array führt möglicherweise zu einer Ausnahme, wenn der Pfad nicht gefunden wird. Anrufen firstund dann auspacken ist also die bessere Lösung

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

Die moderne Empfehlung lautet, NSURLs für Dateien und Verzeichnisse anstelle von NSString-basierten Pfaden zu verwenden:

Geben Sie hier die Bildbeschreibung ein

So erhalten Sie das Dokumentverzeichnis für die App als 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
}

Dies hat eine rudimentäre Fehlerbehandlung, da diese Art davon abhängt, was Ihre Anwendung in solchen Fällen tun wird. Dabei werden jedoch Datei-URLs und eine modernere API verwendet, um die Datenbank-URL zurückzugeben und die ursprüngliche Version aus dem Bundle zu kopieren, falls sie noch nicht vorhanden ist, oder im Fehlerfall eine Null.

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

Normalerweise bevorzuge ich die Verwendung dieser Erweiterung:

Swift 3.x und 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
quelle
wo soll man das nennen?
B. Saravana Kumar
Zu jedem Teil Ihres Codes, den Sie benötigen let path = FileManager.documentsDir()+("/"+"\(fileName)"), können Sie ihn ohne Unterschiede zwischen Thread (Haupt- oder Hintergrund) aufrufen.
Alessandro Ornano
14

Für alle, die ein Beispiel suchen, das mit Swift 2.2 funktioniert, versuchen Abizern-Code mit modernem Fehler, den Fehler zu erfassen

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
}

Update Ich habe verpasst, dass das neue Swift 2.0 Guard hat (Ruby, sofern nicht analog), daher ist es mit Guard viel kürzer und besser lesbar

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

Bequemere Swift 3- Methode:

let documentsUrl = FileManager.default.urls(for: .documentDirectory, 
                                             in: .userDomainMask).first!
Andrey Gordeev
quelle
1
Oder sogarFileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
Iulian Onofrei
7
Ich denke nicht, dass es notwendig ist, jedes Mal einen neuen Dateimanager zu instanziieren, wenn wir eine URL für das Dokumentenverzeichnis erhalten möchten.
Andrey Gordeev
1
Ich dachte, es ist das gleiche. Vielen Dank!
Iulian Onofrei
5

Xcode 8b4 Swift 3.0

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

Normalerweise bevorzuge ich wie unten in Swift 3 , weil ich Dateinamen hinzufügen und eine Datei einfach erstellen kann

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
quelle
1
absoluteStringist falsch. Um den Dateipfad für eine Datei-URL zu erhalten, müssen Sie deren .pathEigenschaft
abrufen