Skip to content

Instantly share code, notes, and snippets.

@katoemba
Last active December 3, 2023 23:17
Show Gist options
  • Save katoemba/d8e806e8bc0a5960a2b0f0e27d2b9adb to your computer and use it in GitHub Desktop.
Save katoemba/d8e806e8bc0a5960a2b0f0e27d2b9adb to your computer and use it in GitHub Desktop.
//
// ContentView.swift
// RigelianTagger
//
// Created by Berrie Kremers on 05/03/2022.
//
// This shows how a directory structure can be shown in a sidebar, with dynamic loading
// of additional content as the user drills down in the hierarchy.
//
import SwiftUI
class FileItem: ObservableObject, Identifiable {
private(set) var name: String
private(set) var url: URL
@Published private(set) var children: [FileItem]?
private var loaded = false
var isDirectory: Bool {
children != nil
}
var isFile: Bool {
children == nil
}
static func root(_ root: URL? = nil) -> FileItem {
let url = root ?? URL(fileURLWithPath: "/Users/berrie/Music", isDirectory: true)
let item = FileItem(name: url.lastPathComponent, url: url, children: [])
// Immediately load the children of this item, so that this data is available when the item is opened
Task {
await item.loadChildren()
}
return item
}
internal init(name: String, url: URL, children: [FileItem]? = nil) {
self.name = name
self.url = url
self.children = children
}
func reset(_ root: URL) {
loaded = false
url = root
name = root.lastPathComponent
children = []
Task {
await loadChildren()
}
}
func loadChildren() async {
guard loaded == false, isDirectory else { return }
loaded = true
do {
let sortedChildren: [FileItem] = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]).map {
let child = FileItem(name: $0.lastPathComponent, url: $0)
if $0.hasDirectoryPath {
child.children = []
}
return child
}
.sorted(by: { $0.name < $1.name })
DispatchQueue.main.async {
self.children = sortedChildren
}
}
catch {
print("\(error)")
}
}
var id: String { url.absoluteString }
}
private func toggleSidebar() {
NSApp.keyWindow?.contentViewController?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
}
struct Sidebar: View {
@ObservedObject var root = FileItem.root()
var body: some View {
List([root], children: \.children) { item in
NavigationLink(destination: Text("Selected " + item.name)) {
HStack {
Image(systemName: item.isDirectory ? "folder" : "doc")
Text(item.name)
}
.onAppear {
// Whenever an item appears, load its children.
Task {
await item.loadChildren()
}
}
}
}
.listStyle(.sidebar)
.frame(minWidth: 150)
.toolbar {
Button(action: toggleSidebar, label: {
Image(systemName: "sidebar.left").help("Toggle Sidebar")
})
Button("Select Root") {
let openPanel = NSOpenPanel()
openPanel.prompt = "Select Directory"
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = true
openPanel.canCreateDirectories = false
openPanel.canChooseFiles = false
openPanel.begin { (result) -> Void in
if result.rawValue == NSApplication.ModalResponse.OK.rawValue {
guard let url = openPanel.url else { return }
root.reset(url)
}
}
}
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
Sidebar()
Text("Main Window")
.frame(minWidth: 200)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
@ezathashim
Copy link

  • very nice - thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment