目前(截至 2022 年 7 月),在 SwiftUI 和 macOS 中实现可以懒加载子项的大纲视图没有明显的方法。请参阅此 stackoverflow 问题。
本软件包包含一个 OutlineView
,它可以实现这个功能。它不是最漂亮的,但如果有人有兴趣使用它,我会清理它并添加功能。
使用示例
import SwiftUI
import EncodiaOutlineView
struct ContentView: View {
@State var roots: [Thing] = makeOutlineRoots()
@StateObject var selection = OutlineViewSingleSelectionModel<Thing>()
// @StateObject var selection = OutlineViewMultiSelectionModel<Thing>()
@State var expansion: Set<UUID> = []
var body: some View {
HStack {
ScrollView {
// OutlineView(roots: roots, expansion: $expansion) { node in
OutlineView(roots: roots, selection: selection, expansion: $expansion) { node in
Text(node.name)
}
}
.frame(width: 200)
Divider()
Text("Some detail view would go here.")
.frame(maxWidth: .infinity)
}
}
}
@MainActor func makeOutlineRoots() -> [Thing] {
var roots = [Thing]()
for i in 1...5 {
let t = Thing(name: "Thing \(i)")
roots.append( t )
}
return roots
}
final class Thing: TreeNodeProtocol {
let id: UUID
let depth: Int
let name: String
let color: Color
init(name: String, depth: Int = 0) {
self.id = UUID()
self.name = name
self.depth = depth
self.color = Color(hue: Double.random(in: 0...1), saturation: 0.7, brightness: 0.7, opacity: 0.5)
}
var canHaveChildren: Bool { depth < 5 }
var isLoadingChildren: Bool { false }
/// Lazy computed property
var children: [Thing] {
if depth >= 5 { return [] }
if _children == nil {
print("Computing children property, name=\(name), depth=\(depth)")
_children = (1...5).map { n in
Thing(name: "\(name).\(n)", depth:depth+1)
}
}
return _children!
}
private var _children: [Thing]? = nil
}
这是另一个使用懒加载来查看文件系统的示例。如果您在 Xcode 中尝试此示例,您可能需要在目标的“Signing and Capabilities”选项卡下禁用沙盒。
struct FileSystemView: View {
var fileSystemRoots = [FileSystemNode(url: URL(fileURLWithPath: "/"))]
@StateObject var selection = OutlineViewSingleSelectionModel<FileSystemNode>()
// @StateObject var selection = OutlineViewMultiSelectionModel<Thing>()
@State var expansion: Set<URL> = []
@State var detailText: String = "??"
var body: some View {
HStack(spacing: 0) {
ScrollView {
// OutlineView(roots: roots, expansion: $expansion) { node in
OutlineView(roots: fileSystemRoots, selection: selection, expansion: $expansion, onNodeClick: onNodeClick) { node in
Text(node.name)
}
}
.frame(width: 350)
Divider()
Text("Some detail view for \(detailText) would go here.")
.frame(maxWidth: .infinity)
}
}
func onNodeClick(_ node: FileSystemNode) {
detailText = node.url.description
}
}
final class FileSystemNode: TreeNodeProtocol {
let url: URL
init(url: URL) {
self.url = url
}
// Needs to implement Identifiable.
var id: URL { url }
var name: String {
url.lastPathComponent
}
var isDirectory: Bool {
// Is there a better `isDirectory` check in Swift these days?
var isDir: ObjCBool = false
if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir) {
return isDir.boolValue
}
else {
return false
}
}
var canHaveChildren: Bool { isDirectory }
@Published var isLoadingChildren: Bool = false
/// Lazy computed property
var children: [FileSystemNode] {
if let cached = _children {
return cached
} else {
if !isLoadingChildren {
isLoadingChildren = true
DispatchQueue.global(qos: .userInitiated).async { [self] in
let childNodes = loadChildNodes()
DispatchQueue.main.async { [self] in
_children = childNodes
isLoadingChildren = false
}
}
}
return []
}
}
private func loadChildNodes() -> [FileSystemNode] {
do {
// Simulate something that takes longer
Thread.sleep(forTimeInterval: 3.0)
let urls = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: [])
let nodes = urls.map { url in FileSystemNode(url: url)}
return nodes
} catch let err {
print("Error reading directory: \(url)")
return []
}
}
@Published private var _children: [FileSystemNode]? = nil
}