使用NSOutlineView作为文件系统目录浏览器的Swift代码

时间:2014-07-18 15:23:20

标签: directory swift nsoutlineview

我已经在使用这个Swift代码已经有一段时间了,并且没有发现问题。代码 下面应该为NSOutlineView提供文件目录作为数据源。 GUI非常简单 只是一个带有NSOutlineView和OutlineViewController实例的Object的窗口。 当我启动应用程序时,它会显示根条目,当我展开根条目时,它会在短时间内显示子项。然后应用程序崩溃,文件中出现错误" main.swift"在线" NSApplicationMain(C_ARGC,C_ARGV) - > " EXC_BAD_ACCESS(代码= EXC_I386_GPFLT)" ?

如果添加了一些println()来证明目录结构 - 这似乎没问题。

快速代码:

import Cocoa
import Foundation

class FileSystemItem {

    let propertyKeys = [NSURLLocalizedNameKey, NSURLEffectiveIconKey, NSURLIsPackageKey, NSURLIsDirectoryKey,NSURLTypeIdentifierKey]
    let fileURL: NSURL

    var name: String! {
    let resourceValues = fileURL.resourceValuesForKeys([NSURLNameKey], error: nil)
        return resourceValues[NSURLNameKey] as? NSString
    }

    var localizedName: String! {
    let resourceValues = fileURL.resourceValuesForKeys([NSURLLocalizedNameKey], error: nil)
        return resourceValues[NSURLLocalizedNameKey] as? NSString
    }

    var icon: NSImage! {
    let resourceValues = fileURL.resourceValuesForKeys([NSURLEffectiveIconKey], error: nil)
        return resourceValues[NSURLEffectiveIconKey] as? NSImage
    }

    var dateOfCreation: NSDate! {
    let resourceValues = self.fileURL.resourceValuesForKeys([NSURLCreationDateKey], error: nil)
        return resourceValues[NSURLCreationDateKey] as? NSDate
    }

    var dateOfLastModification: NSDate! {
    let resourceValues = fileURL.resourceValuesForKeys([NSURLContentModificationDateKey], error: nil)
        return resourceValues[NSURLContentModificationDateKey] as? NSDate
    }

    var typeIdentifier: String! {
    let resourceValues = fileURL.resourceValuesForKeys([NSURLTypeIdentifierKey], error: nil)
        return resourceValues[NSURLTypeIdentifierKey] as? NSString
    }

    var isDirectory: String! {
    let resourceValues = fileURL.resourceValuesForKeys([NSURLIsDirectoryKey], error: nil)
        return resourceValues[NSURLIsDirectoryKey] as? NSString
    }

    var children: [FileSystemItem] {

        var childs: [FileSystemItem] = []
        var isDirectory: ObjCBool = ObjCBool(1)
        let fileManager = NSFileManager.defaultManager()
        var checkValidation = NSFileManager.defaultManager()

        if (checkValidation.fileExistsAtPath(fileURL.relativePath)) {

            if let itemURLs = fileManager.contentsOfDirectoryAtURL(fileURL, includingPropertiesForKeys:propertyKeys, options:.SkipsHiddenFiles, error:nil) {

                for fsItemURL in itemURLs as [NSURL] {

                    if (fileManager.fileExistsAtPath(fsItemURL.relativePath, isDirectory: &isDirectory))
                    {
                        if(isDirectory == true) {
                            let checkItem = FileSystemItem(fileURL: fsItemURL)
                            childs.append(checkItem)
                        }
                    }

                }
            }
        }
        return childs
    }

    init (fileURL: NSURL) {
        self.fileURL = fileURL
    }

    func hasChildren() -> Bool {
        return self.children.count > 0
    }

}


class OutlineViewController : NSObject, NSOutlineViewDataSource {

    let rootFolder : String = "/"
    let rootfsItem : FileSystemItem
    let fsItemURL : NSURL
    let propertyKeys = [NSURLLocalizedNameKey, NSURLEffectiveIconKey, NSURLIsPackageKey, NSURLIsDirectoryKey,NSURLTypeIdentifierKey]

    init() {

        self.fsItemURL = NSURL.fileURLWithPath(rootFolder)
        self.rootfsItem = FileSystemItem(fileURL: fsItemURL)

        for fsItem in rootfsItem.children as [FileSystemItem] {
            for fsSubItem in fsItem.children as [FileSystemItem] {
                println("\(fsItem.name) - \(fsSubItem.name)")
            }

        }
    }

    func outlineView(outlineView: NSOutlineView!, numberOfChildrenOfItem item: AnyObject!) -> Int {
        if let theItem: AnyObject = item {
            let tmpfsItem: FileSystemItem = item as FileSystemItem
            return tmpfsItem.children.count
        }
        return 1
    }

    func outlineView(outlineView: NSOutlineView!, isItemExpandable item: AnyObject!) -> Bool {
        if let theItem: AnyObject = item {
            let tmpfsItem: FileSystemItem = item as FileSystemItem
            return tmpfsItem.hasChildren()
        }
        return false
    }

    func outlineView(outlineView: NSOutlineView!, child index: Int, ofItem item: AnyObject!) -> AnyObject! {
        if let theItem: AnyObject = item {
            let tmpfsItem: FileSystemItem = item as FileSystemItem
            return tmpfsItem.children[index]
        }
        return rootfsItem
    }

    func outlineView(outlineView: NSOutlineView!, objectValueForTableColumn tableColumn: NSTableColumn!, byItem item: AnyObject!) -> AnyObject! {
        if let theItem: AnyObject = item {
            let tmpfsItem: FileSystemItem = item as FileSystemItem
            return tmpfsItem.localizedName
        }
        return "-empty-"
    }
}



class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet var window: NSWindow

    func applicationDidFinishLaunching(aNotification: NSNotification?) {
        // Insert code here to initialize your application

    }

    func applicationWillTerminate(aNotification: NSNotification?) {
        // Insert code here to tear down your application

    }
}

任何提示?

5 个答案:

答案 0 :(得分:4)

我在NSOutlineView上有一个与EXC_BAD_ACCESS类似的问题 - 带有NSOutlineViewDataSource。扩展节点时的相同行为,显示数据然后发生崩溃。仪器中的一些分析显示在某处创建了一个Zombie对象,然后Outline视图尝试访问它。

我认为这是一个错误 - 但我设法通过改变所有Swift' Strings'来解决这个问题。到了NSStrings'。如果您正在使用它们,则可能必须对所有Swift类型执行此操作。

为了确保一切都是NSString,我必须在类中声明常量,例如:

var empty_string : NSString = ""

因为任何时候我喂它一个Swift字符串都会崩溃。哦,希望这将在未来得到解决!

答案 1 :(得分:4)

所以,只是为了澄清发生了什么。 NSOutlineView不保留为其“模型”提供的对象;总是希望客户保留它们。对于ARC代码,这不能很好地工作,因为如果向NSOutlineView方法返回一个新实例,该对象将不会被任何东西保留,并且很快就会被释放。然后随后的outlineView委托方法触摸这些对象将导致崩溃。解决方法是将对象保留在自己的数组中。

请注意,从objectValueForTableColumn返回的对象由NSControl的objectValue保留。

回到Swift:正如Thomas所说,对象必须是objc对象,因为它们被桥接到objc类。 Swift字符串隐式桥接到临时NSString。由于上述问题,这会导致崩溃,因为没有任何东西会保留NSString实例。这就是维护一系列NSStrings“解决”这个问题的原因。

解决方案是NSOutlineView可以选择保留给它的项目。请考虑通过bugreporter.apple.com

记录错误请求

谢谢, corbin(我在NSOutlineView上工作)

答案 2 :(得分:2)

似乎

outlineView(outlineView: NSOutlineView!, objectValueForTableColumn tableColumn: NSTableColumn!, byItem item: AnyObject!) -> AnyObject!

需要返回符合obj-c协议的对象。所以你可以返回

@objc class MyClass {
  ...
}

(或NSString等)。但不是StringArray等原生的Swift内容。

答案 3 :(得分:0)

我相信这里遇到的一个问题是,每次访问子属性时,“children”数组都会被替换。

我认为这会导致NSOutlineView中的一些弱引用在查询DataSource以获取信息时中断。

如果你缓存“孩子”并访问缓存来计算“numberOfChildren”和“getChildForIndex”,你会看到一个改进。

答案 4 :(得分:0)

在Swift 3.0中,我使用了以下代码,它编译并运行没有问题。由于我正试图将TreeTest翻译成斯威夫特,所以它远非完整,而是朝着正确的方向迈出了一步。

import Cocoa
import Foundation

class FileSystemItem: NSObject {

let propertyKeys: [URLResourceKey] = [.localizedNameKey, .effectiveIconKey, .isDirectoryKey, .typeIdentifierKey]
var fileURL: URL

var name: String! {
    let resourceValues = try! fileURL.resourceValues(forKeys: [.nameKey])
    return resourceValues.name
}

var localizedName: String! {
    let resourceValues = try! fileURL.resourceValues(forKeys: [.localizedNameKey])
    return resourceValues.localizedName
}

var icon: NSImage! {
    let resourceValues = try! fileURL.resourceValues(forKeys: [.effectiveIconKey])
    return resourceValues.effectiveIcon as? NSImage
}

var dateOfCreation: Date! {
    let resourceValues = try! fileURL.resourceValues(forKeys: [.creationDateKey])
    return resourceValues.creationDate
}

var dateOfLastModification: Date! {
    let resourceValues = try! fileURL.resourceValues(forKeys: [.contentModificationDateKey])
    return resourceValues.contentAccessDate
}

var typeIdentifier: String! {
    let resourceValues = try! fileURL.resourceValues(forKeys: [.typeIdentifierKey])
    return resourceValues.typeIdentifier
}

var isDirectory: Bool! {
    let resourceValues = try! fileURL.resourceValues(forKeys: [.isDirectoryKey])
    return resourceValues.isDirectory
}

init(url: Foundation.URL) {
    self.fileURL = url
}

var children: [FileSystemItem] {
    var childs: [FileSystemItem] = []
    let fileManager = FileManager.default
    // show no hidden Files (if you want this, comment out next line)
    // let options = FileManager.DirectoryEnumerationOptions.skipsHiddenFiles
    var directoryURL = ObjCBool(false)
    let validURL = fileManager.fileExists(atPath: fileURL.relativePath, isDirectory: &directoryURL)
    if (validURL && directoryURL.boolValue) {
        // contents of directory
        do {
            let childURLs = try
                fileManager.contentsOfDirectory(at: fileURL, includingPropertiesForKeys: propertyKeys, options: [])
            for childURL in childURLs {
                let child = FileSystemItem(url: childURL)
                childs.append(child)
            }
        }
        catch {
            print("Unexpected error occured: \(error).")
        }
    }
    return childs
}

func hasChildren() -> Bool {
    return self.children.count > 0
}
}

class OutLineViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource {

@IBOutlet weak var outlineView: NSOutlineView!
@IBOutlet weak var pathController: NSPathControl!

var fileSystemItemURL: URL!
let propertyKeys: [URLResourceKey] = [.localizedNameKey, .effectiveIconKey, .isDirectoryKey, .typeIdentifierKey]
var rootfileSystemItem: FileSystemItem!
var rootURL: URL!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    let userDirectoryURL = URL(fileURLWithPath: NSHomeDirectory())
    // directory "Pictures" is set as root
    let rootURL = userDirectoryURL.appendingPathComponent("Pictures", isDirectory: true)
    self.pathController.url = rootURL
    self.rootfileSystemItem = FileSystemItem(url: rootURL)

    for fileSystemItem in rootfileSystemItem.children as [FileSystemItem] {
        for subItem in fileSystemItem.children as [FileSystemItem] {
            print("\(fileSystemItem.name) - \(subItem.name)")
        }
    }
//FileSystemItem.rootItemWithPath(self.pathControl.URL.path)
//self.searchForFilesInDirectory(picturesPath)
}

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

@IBAction func pathControllerAction(_ sender: NSPathControl) {
    print("controller clicked")
}

// MARK: - outline data source methods

func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
    if let fileSystemItem = item as? FileSystemItem {
        return fileSystemItem.children.count
    }
    return 1
}

func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
    if let fileSystemItem = item as? FileSystemItem {
        return fileSystemItem.hasChildren()
    }
    return false
}

func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
    if let fileSystemItem = item as? FileSystemItem {
        return fileSystemItem.children[index]
    }
    return rootfileSystemItem
}

func outlineView(_ outlineView: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? {
    if let fileSystemItem = item as? FileSystemItem {
        switch tableColumn?.identifier {
        case "tree"?:
            return fileSystemItem.localizedName
        case "coordinate"?:
            return " empty "
        default:
            break
        }
    }
    return " -empty- "
}

// MARK: - outline view delegate methods

func outlineView(_ outlineView: NSOutlineView, shouldEdit tableColumn: NSTableColumn?, item: Any) -> Bool {
    return false
}
}

通过新编辑,大纲视图现在显示所有文件和目录。您可以在FileSystemItem类的子部分中影响外观。