Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ struct ContentView: View {
@Environment(\.colorScheme) var colorScheme

@StateObject var dataSource = sampleDataSource()
@State var selection: FileItem?
@State var selections: Set<FileItem> = []
@State var separatorColor: Color = Color(NSColor.separatorColor)
@State var separatorEnabled = false

Expand All @@ -33,7 +33,7 @@ struct ContentView: View {
var outlineView: some View {
OutlineView(
dataSource.rootData,
selection: $selection,
selections: $selections,
children: dataSource.childrenOfItem,
separatorInsets: { fileItem in
NSEdgeInsets(
Expand Down
213 changes: 200 additions & 13 deletions Sources/OutlineView/OutlineView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ enum ChildSource<Data: Sequence> {

@available(macOS 10.15, *)
public struct OutlineView<Data: Sequence, Drop: DropReceiver>: NSViewControllerRepresentable
where Drop.DataElement == Data.Element {
where Drop.DataElement == Data.Element, Data.Element: Hashable {
public typealias NSViewControllerType = OutlineViewController<Data, Drop>

let data: Data
let childSource: ChildSource<Data>
@Binding var selection: Data.Element?
@Binding var selections: Set<Data.Element>
var content: (Data.Element) -> NSView
var separatorInsets: ((Data.Element) -> NSEdgeInsets)?

var allowsMultipleSelection = false
/// Outline view style is unavailable on macOS 10.15 and below.
/// Stored as `Any` to make the property available on all platforms.
private var _styleStorage: Any?
Expand Down Expand Up @@ -55,7 +55,7 @@ where Drop.DataElement == Data.Element {
data: data,
childrenSource: childSource,
content: content,
selectionChanged: { selection = $0 },
selectionChanged: { selections = $0 },
separatorInsets: separatorInsets)
controller.setIndentation(to: indentation)
if #available(macOS 11.0, *) {
Expand All @@ -69,12 +69,13 @@ where Drop.DataElement == Data.Element {
context: Context
) {
outlineController.updateData(newValue: data)
outlineController.changeSelectedItem(to: selection)
outlineController.changeSelectedItem(to: selections)
outlineController.setRowSeparator(visibility: separatorVisibility)
outlineController.setRowSeparator(color: separatorColor)
outlineController.setDragSourceWriter(dragDataSource)
outlineController.setDropReceiver(dropReceiver)
outlineController.setAcceptedDragTypes(acceptedDropTypes)
outlineController.setAllowsMultipleSelection(allowsMultipleSelection)
}
}

Expand Down Expand Up @@ -184,9 +185,31 @@ public extension OutlineView {
) {
self.data = data
self.childSource = .keyPath(children)
self._selection = selection
self._selections = .init {
if let sel = selection.wrappedValue {
return Set([sel])
}

return Set()
} set: { newValue in
selection.wrappedValue = newValue.first
}
self.separatorVisibility = .hidden
self.content = content
}

init(
_ data: Data,
children: KeyPath<Data.Element, Data?>,
selections: Binding<Set<Data.Element>>,
content: @escaping (Data.Element) -> NSView
) {
self.data = data
self.childSource = .keyPath(children)
self._selections = selections
self.separatorVisibility = .hidden
self.content = content
self.allowsMultipleSelection = true
}

/// Creates an `OutlineView` from a collection of root data elements and
Expand Down Expand Up @@ -225,11 +248,33 @@ public extension OutlineView {
content: @escaping (Data.Element) -> NSView
) {
self.data = data
self._selection = selection
self._selections = .init {
if let sel = selection.wrappedValue {
return Set([sel])
}

return Set()
} set: { newValue in
selection.wrappedValue = newValue.first
}
self.childSource = .provider(children)
self.separatorVisibility = .hidden
self.content = content
}

init(
_ data: Data,
selections: Binding<Set<Data.Element>>,
children: @escaping (Data.Element) -> Data?,
content: @escaping (Data.Element) -> NSView
) {
self.data = data
self._selections = selections
self.childSource = .provider(children)
self.separatorVisibility = .hidden
self.content = content
self.allowsMultipleSelection = true
}
}

// MARK: Initializers for macOS 10.15 and higher with NoDropReceiver.
Expand Down Expand Up @@ -273,9 +318,31 @@ public extension OutlineView where Drop == NoDropReceiver<Data.Element> {
) {
self.data = data
self.childSource = .keyPath(children)
self._selection = selection
self._selections = .init {
if let sel = selection.wrappedValue {
return Set([sel])
}

return Set()
} set: { newValue in
selection.wrappedValue = newValue.first
}
self.separatorVisibility = .hidden
self.content = content
}

init(
_ data: Data,
children: KeyPath<Data.Element, Data?>,
selections: Binding<Set<Data.Element>>,
content: @escaping (Data.Element) -> NSView
) {
self.data = data
self.childSource = .keyPath(children)
self._selections = selections
self.separatorVisibility = .hidden
self.content = content
self.allowsMultipleSelection = true
}

/// Creates an `OutlineView` from a collection of root data elements and
Expand Down Expand Up @@ -314,11 +381,33 @@ public extension OutlineView where Drop == NoDropReceiver<Data.Element> {
content: @escaping (Data.Element) -> NSView
) {
self.data = data
self._selection = selection
self._selections = .init {
if let sel = selection.wrappedValue {
return Set([sel])
}

return Set()
} set: { newValue in
selection.wrappedValue = newValue.first
}
self.childSource = .provider(children)
self.separatorVisibility = .hidden
self.content = content
}

init(
_ data: Data,
selections: Binding<Set<Data.Element>>,
children: @escaping (Data.Element) -> Data?,
content: @escaping (Data.Element) -> NSView
) {
self.data = data
self._selections = selections
self.childSource = .provider(children)
self.separatorVisibility = .hidden
self.content = content
self.allowsMultipleSelection = true
}
}

// MARK: Initializers for macOS 11 and higher.
Expand Down Expand Up @@ -367,11 +456,36 @@ public extension OutlineView {
) {
self.data = data
self.childSource = .keyPath(children)
self._selection = selection
self._selections = .init {
if let sel = selection.wrappedValue {
return Set([sel])
}

return Set()
} set: { newValue in
selection.wrappedValue = newValue.first
}
self.separatorInsets = separatorInsets
self.separatorVisibility = separatorInsets == nil ? .hidden : .visible
self.content = content
}

@available(macOS 11.0, *)
init(
_ data: Data,
children: KeyPath<Data.Element, Data?>,
selections: Binding<Set<Data.Element>>,
separatorInsets: ((Data.Element) -> NSEdgeInsets)? = nil,
content: @escaping (Data.Element) -> NSView
) {
self.data = data
self.childSource = .keyPath(children)
self._selections = selections
self.separatorInsets = separatorInsets
self.separatorVisibility = separatorInsets == nil ? .hidden : .visible
self.content = content
self.allowsMultipleSelection = true
}

/// Creates an `OutlineView` from a collection of root data elements and
/// a closure that provides children to each element.
Expand Down Expand Up @@ -414,12 +528,37 @@ public extension OutlineView {
content: @escaping (Data.Element) -> NSView
) {
self.data = data
self._selection = selection
self._selections = .init {
if let sel = selection.wrappedValue {
return Set([sel])
}

return Set()
} set: { newValue in
selection.wrappedValue = newValue.first
}
self.childSource = .provider(children)
self.separatorInsets = separatorInsets
self.separatorVisibility = separatorInsets == nil ? .hidden : .visible
self.content = content
}

@available(macOS 11.0, *)
init(
_ data: Data,
selections: Binding<Set<Data.Element>>,
children: @escaping (Data.Element) -> Data?,
separatorInsets: ((Data.Element) -> NSEdgeInsets)? = nil,
content: @escaping (Data.Element) -> NSView
) {
self.data = data
self._selections = selections
self.childSource = .provider(children)
self.separatorInsets = separatorInsets
self.separatorVisibility = separatorInsets == nil ? .hidden : .visible
self.content = content
self.allowsMultipleSelection = true
}
}

// MARK: Initializers for macOS 11 and higher with NoDropReceiver.
Expand Down Expand Up @@ -467,11 +606,35 @@ public extension OutlineView where Drop == NoDropReceiver<Data.Element> {
) {
self.data = data
self.childSource = .keyPath(children)
self._selection = selection
self._selections = .init {
if let sel = selection.wrappedValue {
return Set([sel])
}

return Set()
} set: { newValue in
selection.wrappedValue = newValue.first
}
self.separatorInsets = separatorInsets
self.separatorVisibility = separatorInsets == nil ? .hidden : .visible
self.content = content
}

init(
_ data: Data,
children: KeyPath<Data.Element, Data?>,
selections: Binding<Set<Data.Element>>,
separatorInsets: ((Data.Element) -> NSEdgeInsets)? = nil,
content: @escaping (Data.Element) -> NSView
) {
self.data = data
self.childSource = .keyPath(children)
self._selections = selections
self.separatorInsets = separatorInsets
self.separatorVisibility = separatorInsets == nil ? .hidden : .visible
self.content = content
self.allowsMultipleSelection = true
}

/// Creates an `OutlineView` from a collection of root data elements and
/// a closure that provides children to each element.
Expand Down Expand Up @@ -513,10 +676,34 @@ public extension OutlineView where Drop == NoDropReceiver<Data.Element> {
content: @escaping (Data.Element) -> NSView
) {
self.data = data
self._selection = selection
self._selections = .init {
if let sel = selection.wrappedValue {
return Set([sel])
}

return Set()
} set: { newValue in
selection.wrappedValue = newValue.first
}
self.childSource = .provider(children)
self.separatorInsets = separatorInsets
self.separatorVisibility = separatorInsets == nil ? .hidden : .visible
self.content = content
}

init(
_ data: Data,
selections: Binding<Set<Data.Element>>,
children: @escaping (Data.Element) -> Data?,
separatorInsets: ((Data.Element) -> NSEdgeInsets)? = nil,
content: @escaping (Data.Element) -> NSView
) {
self.data = data
self._selections = selections
self.childSource = .provider(children)
self.separatorInsets = separatorInsets
self.separatorVisibility = separatorInsets == nil ? .hidden : .visible
self.content = content
self.allowsMultipleSelection = true
}
}
14 changes: 9 additions & 5 deletions Sources/OutlineView/OutlineViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Cocoa

@available(macOS 10.15, *)
public class OutlineViewController<Data: Sequence, Drop: DropReceiver>: NSViewController
where Drop.DataElement == Data.Element {
where Drop.DataElement == Data.Element, Data.Element: Hashable {
let outlineView = NSOutlineView()
let scrollView = NSScrollView(frame: NSRect(x: 0, y: 0, width: 400, height: 400))

Expand All @@ -16,7 +16,7 @@ where Drop.DataElement == Data.Element {
data: Data,
childrenSource: ChildSource<Data>,
content: @escaping (Data.Element) -> NSView,
selectionChanged: @escaping (Data.Element?) -> Void,
selectionChanged: @escaping (Set<Data.Element>) -> Void,
separatorInsets: ((Data.Element) -> NSEdgeInsets)?
) {
scrollView.documentView = outlineView
Expand All @@ -28,7 +28,7 @@ where Drop.DataElement == Data.Element {
outlineView.headerView = nil
outlineView.usesAutomaticRowHeights = true
outlineView.columnAutoresizingStyle = .uniformColumnAutoresizingStyle

let onlyColumn = NSTableColumn()
onlyColumn.resizingMask = .autoresizingMask
outlineView.addTableColumn(onlyColumn)
Expand Down Expand Up @@ -96,9 +96,9 @@ extension OutlineViewController {
dataSource.rebuildIDTree(rootItems: newState, outlineView: outlineView)
}

func changeSelectedItem(to item: Data.Element?) {
func changeSelectedItem(to items: Set<Data.Element>) {
delegate.changeSelectedItem(
to: item.map { OutlineViewItem(value: $0, children: childrenSource) },
to: items.map { OutlineViewItem(value: $0, children: childrenSource) },
in: outlineView)
}

Expand Down Expand Up @@ -145,4 +145,8 @@ extension OutlineViewController {
outlineView.registerForDraggedTypes(acceptedTypes)
}
}

func setAllowsMultipleSelection(_ allowsMultipleSelection: Bool) {
outlineView.allowsMultipleSelection = allowsMultipleSelection
}
}
Loading