alex
alex

Reputation: 163

Check if NSURL is a directory

While using Swift I want to check if an NSURL location is a directory. With Objective-C this is not a problem and working find, but when I convert the code to Swift I run into a runtime error.

Maybe someone can point me in the right direction?

import Foundation

let defaultManager = NSFileManager.defaultManager()
let documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as NSURL
let localDocumentURLs = defaultManager.contentsOfDirectoryAtURL(documentsDirectory,
includingPropertiesForKeys: nil, options: .SkipsPackageDescendants, error: nil) as NSURL[]

for url in localDocumentURLs {
    var isError: NSError? = nil
    var isDirectory: AutoreleasingUnsafePointer<AnyObject?> = nil
    var success: Bool = url.getResourceValue(isDirectory, forKey: NSURLIsDirectoryKey, error: &isError)
}

Upvotes: 12

Views: 16250

Answers (7)

vadian
vadian

Reputation: 285270

In iOS 9.0+ and macOS 10.11+ there is a property in NSURL / URL

Swift:

var hasDirectoryPath: Bool { get }

Objective-C:

@property(readonly) BOOL hasDirectoryPath;

However this is only reliable for URLs created with the FileManager API which ensures that the string path of a dictionary ends with a slash.

For URLs created with custom literal string paths reading the resource value isDirectory is preferable

Swift:

let isDirectory = (try? url.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory ?? false

Objective-C:

NSNumber *isDirectory = nil;
[url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil];
NSLog(@"%i", isDirectory.boolValue);

Upvotes: 36

Madmoron
Madmoron

Reputation: 188

Swift 3

extension URL {
    var isDirectory: Bool {
        let values = try? resourceValues(forKeys: [.isDirectoryKey])
        return values?.isDirectory ?? false
    }
}

Upvotes: 17

Klaas
Klaas

Reputation: 22773

... and this is a Swift 3 extension on FileManager:

extension FileManager {
    func isDirectory(url:URL) -> Bool? {
        var isDir: ObjCBool = ObjCBool(false)
        if fileExists(atPath: url.path, isDirectory: &isDir) {
            return isDir.boolValue
        } else {
            return nil
        }
    }
}

Upvotes: 0

Mecki
Mecki

Reputation: 133199

Plenty of answers here... but they all have one thing in common:
They all use FileManager (which is in fact NSFileManger).

This approach has one downside: FileManager is not thread-safe! Using a FileManager from different threads in parallel usually works for stateless operations, but that is nowhere guaranteed (you might be building on undefined behavior when you rely that this works).

A safe implementation would have to create a new FileManager instance every time, yet that would be horrible performance, or use one per thread, which is huge management overhead.

There's a much better, much simpler approach I'd like to present here, that doesn't need any 3rd party class:

extension URL {
    var isDirectory: Bool? {
        do {
            let values = try self.resourceValues(
                forKeys:Set([URLResourceKey.isDirectoryKey])
            )
            return values.isDirectory
        } catch  { return nil }
    }
}

That's it. Using the resourceValues() method you can also easily query other interesting information, like the size of a file or the last modification date. This is a very powerful API, that exists for ages for NSURL in Obj-C as well.

Upvotes: 4

Eli Burke
Eli Burke

Reputation: 2789

Here's the swift 2.0 version of the original poster's code:

        do {
            var rsrc: AnyObject?
            try element.getResourceValue(&rsrc, forKey: NSURLIsDirectoryKey)
            if let isDirectory = rsrc as? NSNumber {
                if isDirectory == true {
                    // ...
                }
            }
        } catch {
        }

Upvotes: 3

BananaAcid
BananaAcid

Reputation: 3501

my 2 cents ... swift NSURL way: (based on holex's answer)

var error: NSError?

// since getting it as URLForDirectory, it flags it as dir, for custom path: NSURL:fileURLWithPath:isDirectory:
let documentURL:NSURL = NSFileManager.defaultManager().URLForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomain: NSSearchPathDomainMask.UserDomainMask, appropriateForURL: nil, create: true, error: &error)

// passes true, if it exists and validates as dir (as flagged in the NSURL object)
if (documentURL.checkResourceIsReachableAndReturnError(&error) {
    println(error)
}

Upvotes: 2

holex
holex

Reputation: 24031

that is working quote well on my side:

var error: NSError?
let documentURL : NSURL = NSFileManager.defaultManager().URLForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomain: NSSearchPathDomainMask.UserDomainMask, appropriateForURL: nil, create: true, error: &error)
var isDirectory: ObjCBool = ObjCBool(0)
if NSFileManager.defaultManager().fileExistsAtPath(documentURL.path, isDirectory: &isDirectory) {
    println(isDirectory)
}

NOTE: it checks whether the Documents folder is a folder. you can replace the URL with anything, of course.

Upvotes: 14

Related Questions