如何自动展开NSOutlineView目录路径以恢复用户上次访问位置?
解决方案:自动展开目录路径并恢复上次访问位置
针对你提出的两个技术问题,结合你开发的目录展示应用需求,我会一步步给出修改方案,实现自动展开路径和恢复上次访问位置的功能。
1. 先修复DirectoryItem的父节点关联
首先,我们需要给DirectoryItem添加父节点引用,这样才能从任意子节点追溯到顶层父节点。修改你的DirectoryItem类:
class DirectoryItem { var name: String var url: URL var isdir: Bool var prev: URL weak var parent: DirectoryItem? // 新增:记录父节点,用weak避免循环引用 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]) var bb: [DirectoryItem] = [] // 创建子节点时传入当前节点作为父节点 let aa = urls.map { DirectoryItem(url: $0, parent: self) } for a in aa { if(a.isdir) { bb.append(a) } } return bb } catch let error as NSError { return [] } }() // 修改初始化方法,支持传入父节点 init(url: URL, parent: DirectoryItem? = nil) { self.url = url self.name = url.lastPathComponent self.isdir = url.hasDirectoryPath self.prev = url.deletingLastPathComponent() self.parent = parent } }
2. 添加工具方法:获取节点到根的完整路径
给DirectoryItem扩展一个方法,用来获取从当前节点到根节点的所有节点(顺序是根→目标节点):
extension DirectoryItem { func getPathToRoot() -> [DirectoryItem] { var path: [DirectoryItem] = [] var currentItem: DirectoryItem? = self // 从当前节点往上遍历父节点,直到根 while let item = currentItem { path.insert(item, at: 0) // 插入到数组开头,保证路径顺序是根到目标 currentItem = item.parent } return path } }
3. 实现自动展开路径的方法
在ViewController中添加一个方法,用来展开指定节点的完整路径,并选中目标节点:
private func expandPath(to targetItem: DirectoryItem) { let pathItems = targetItem.getPathToRoot() // 逐个展开路径上的所有节点 for item in pathItems { outlineView.expandItem(item) } // 选中目标节点,模拟用户上次访问的位置 if let row = outlineView.row(forItem: targetItem) { outlineView.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false) } }
4. 添加查找节点的方法
需要根据保存的URL找到对应的DirectoryItem节点,添加递归查找方法:
private func findItem(with url: URL, in parentItem: DirectoryItem) -> DirectoryItem? { // 先判断当前父节点是否就是目标节点 if parentItem.url == url { return parentItem } // 触发懒加载的childItems,确保子节点已初始化 _ = parentItem.childItems // 递归查找子节点 for child in parentItem.childItems { if let foundItem = findItem(with: url, in: child) { return foundItem } } return nil }
5. 修改viewDidLoad,恢复上次访问路径
最后,在viewDidLoad中加载完根目录后,查找上次保存的目录并自动展开:
override func viewDidLoad() { super.viewDidLoad() let defaults = UserDefaults.standard let lastDirectory = defaults.url(forKey: "LastDirectory") // 初始化根节点为用户home目录(你可以根据需求修改为其他根目录) rootItem = DirectoryItem(url: FileManager.default.homeDirectoryForCurrentUser) outlineView.dataSource = self outlineView.delegate = self tableView.delegate = self tableView.dataSource = self tableView.doubleAction = #selector(doubleClickOnResultRow) self.progressIndicator.isHidden = true self.tableView.reloadData() // 如果有保存的上次访问目录,查找并展开路径 if let targetUrl = lastDirectory { if let targetItem = findItem(with: targetUrl, in: rootItem) { expandPath(to: targetItem) } } }
对应你的两个问题的说明
- 问题1:从子节点展开至最顶层父节点:通过
getPathToRoot()方法可以获取子节点到根的所有节点,再调用outlineView.expandItem()逐个展开即可,上面的expandPath方法已经实现了这个逻辑。 - 问题2:从根节点展开所有层级至目标节点:同样通过
getPathToRoot()拿到根到目标的路径,逐个展开,同时选中目标节点,这正是恢复上次访问路径的核心逻辑。
这样修改后,用户关闭再打开应用时,就会自动展开到上次访问的目录节点,无需手动点击展开啦~
内容的提问来源于stack exchange,提问作者Renan Aguiar
相关产品推荐
相关产品推荐

