1

I would like to get and show the user home directory on a NSOutlineView going deep every time the node is expandable.

So far I can get only the first level and expand it to a second level.

I imagine that it has to be appended to an index or something like that.

I can get level 1 and 2. When i click on the level 2, for example Documents, there is a directory, yourDirectoryName, that has more directories in it. I would like to show the arrow and be able to keep going further on the tree

First pic is my app. Second pic is an example from filezilla

My app currently shows it

An example of would it has to be

import Cocoa
class Directories {
var name: String
var subDirectories: [String]

init(name: String, subDirectories: [String]) {
    self.name = name
    self.subDirectories = subDirectories
}
}

class ViewController: NSViewController {
var directories = [Directories]()
@IBOutlet weak var outlineView: NSOutlineView!

override func viewDidLoad() {
    super.viewDidLoad()
    getDir(path: "")
    outlineView.dataSource = self
    outlineView.delegate = self
}



func getDir(path: String) {
     let fm = FileManager.default.homeDirectoryForCurrentUser

    do {
        let items = contentsOf(folder: fm)
        for item in items {
            let sub = getSubDir(path: item.lastPathComponent)
            let re = Directories(name: item.lastPathComponent, subDirectories: sub)
            directories.append(re)
        }
    }
}

func contentsOf(folder: URL) -> [URL] {
    let fileManager = FileManager.default
    do {
        let contents = try fileManager.contentsOfDirectory(atPath: folder.path)

        let urls = contents.map { return folder.appendingPathComponent($0) }
        return urls
    } catch {
        return []
    }
}

func getSubDir(path: String) -> [String]{
    var sub = [String]()
    let fm = FileManager.default
    let filePath = NSString(string: path).expandingTildeInPath
    do {
        let items = try fm.contentsOfDirectory(atPath: filePath)
        for item in items {
            sub.append(item)
        }
    } catch {
        // failed to read directory
    }
    return sub
}

override var representedObject: Any? {
    didSet {
        // Update the view, if already loaded.
    }
}
}



extension ViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
    if let directories = item as? Directories {
        return directories.subDirectories[index]
    }
    return directories[index]
}

func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
    if let directories = item as? Directories {
        return directories.subDirectories.count > 0
    }
    return false
}

func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
    if let directories = item as? Directories {
        return directories.subDirectories.count
    }
    return directories.count
}
}

extension ViewController: NSOutlineViewDelegate {
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
    var text = ""
    if let directories = item as? Directories {
        text = directories.name
    }
    else {
        text = item as! String
    }

    let tableCell = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "cell"), owner: self) as! NSTableCellView
    tableCell.textField!.stringValue = text
    return tableCell
}
}
Renan Aguiar
  • 245
  • 5
  • 22
  • Is the question how to make the data a tree or is the question how to load a subdirectory when a node is expanded? – Willeke May 01 '19 at 14:50
  • I can get level 1 and 2. When i click on the level 2, for example Documents, there is a directory, yourDirectoryName, that has more directories in it. I would like to show the arrow and be able to keep going further on the tree. – Renan Aguiar May 01 '19 at 21:40
  • Do you want to load a tree of all subdirectories into `directories` or do you want to load the subdirectory when a node is expanded? – Willeke May 01 '19 at 22:02
  • When it is expanded. For example, if I access an external drive with too many subdirs, it would take a while to read and store all the names... So, i think when clicking on it would be a better aproach – Renan Aguiar May 01 '19 at 22:06
  • An item is expandable if it's a directory. Load the subdirectory when the item is expanded. – Willeke May 02 '19 at 11:14
  • I can get the first level and second level. How do I append more levels when i click on the second level? – Renan Aguiar May 02 '19 at 11:36
  • Call `getSubDir`? – Willeke May 02 '19 at 13:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/192732/discussion-between-renan-aguiar-and-willeke). – Renan Aguiar May 02 '19 at 16:02

1 Answers1

2

Expanding level 2 to 3 is the same as expanding level 1 to 2. A subdirectory is a directory and has its own subdirectories. The subDirectories property of Directories should be an array of Directories. The directories property of ViewController points to a tree of directories and can be a Directories.

Example:

class ViewController: NSViewController {

    // the rootItem is invisible
    var rootItem = DirectoryItem(url: FileManager.default.homeDirectoryForCurrentUser)

}


extension ViewController: NSOutlineViewDataSource {

    func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
        let directoryItem = item as? DirectoryItem ?? rootItem
        return directoryItem.childItems.count
    }

    func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
        let directoryItem = item as? DirectoryItem ?? rootItem
        return directoryItem.childItems[index]
    }

    func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
        let directoryItem = item as? DirectoryItem ?? rootItem
        return directoryItem.isExpandable
    }

}


extension ViewController: NSOutlineViewDelegate {

    func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
        let directoryItem = item as? DirectoryItem ?? rootItem
        let tableCell = outlineView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: self) as! NSTableCellView
        tableCell.textField!.stringValue = directoryItem.name
        return tableCell
    }

}


class DirectoryItem {

    var name: String
    var url: URL

    lazy var isExpandable: Bool = {
        do {
            return try url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory ?? false
        } catch let error as NSError {
            return false
        }
    }()

    lazy var childItems: [DirectoryItem] = {
        do {
            let urls = try FileManager.default.contentsOfDirectory(at: url,
                    includingPropertiesForKeys: [.isDirectoryKey],
                    options: [.skipsHiddenFiles])
            return urls.map { DirectoryItem(url: $0) }
        } catch let error as NSError {
            return []
        }
    }()

    init(url: URL) {
        self.url = url
        self.name = url.lastPathComponent
    }

}

Disclaimer: I'm used to Objective-C and I'm struggling with Swift.

Willeke
  • 14,578
  • 4
  • 19
  • 47
  • i got: Fatal error: Unexpectedly found nil while unwrapping an Optional value – Renan Aguiar May 04 '19 at 04:02
  • i couldnt trace it... but it happens when i run it – Renan Aguiar May 04 '19 at 12:10
  • Maybe this helps: [What does “fatal error: unexpectedly found nil while unwrapping an Optional value” mean?](https://stackoverflow.com/questions/32170456/what-does-fatal-error-unexpectedly-found-nil-while-unwrapping-an-optional-valu) – Willeke May 04 '19 at 13:00
  • Works: extension ViewController: NSOutlineViewDelegate { func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { var text = "" if let directories = item as? DirectoryItem { text = directories.name } else { text = item as! String } let tableCell = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "cell"), owner: self) as! NSTableCellView tableCell.textField!.stringValue = text return tableCell } } – Renan Aguiar May 16 '19 at 03:49