From de03051537d36a574646db7d791f76846727c178 Mon Sep 17 00:00:00 2001 From: Marco Conti Date: Thu, 14 Sep 2017 22:40:41 +0200 Subject: [PATCH] Add table filtering --- Cartfile.private | 1 + Cartfile.resolved | 1 + EasyTables.xcodeproj/project.pbxproj | 30 +++-- EasyTables/ColumnDefinition.swift | 112 ++++++++++++++++++ ...{EasyTable.swift => EasyTableSource.swift} | 37 ------ EasyTables/GenericTableDataSource.swift | 39 ++++-- EasyTablesExample/Base.lproj/Main.storyboard | 13 +- ...roller.swift => TableViewController.swift} | 64 ++++++++-- 8 files changed, 228 insertions(+), 69 deletions(-) create mode 100644 Cartfile.private create mode 100644 EasyTables/ColumnDefinition.swift rename EasyTables/{EasyTable.swift => EasyTableSource.swift} (89%) rename EasyTablesExample/{ViewController.swift => TableViewController.swift} (58%) diff --git a/Cartfile.private b/Cartfile.private new file mode 100644 index 0000000..3cabd50 --- /dev/null +++ b/Cartfile.private @@ -0,0 +1 @@ +github "robb/Cartography" "1.1.0" \ No newline at end of file diff --git a/Cartfile.resolved b/Cartfile.resolved index 2edf8e1..f874f3a 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1,2 @@ github "marcoconti83/ClosureControls" "v0.2.1" +github "robb/Cartography" "1.1.0" diff --git a/EasyTables.xcodeproj/project.pbxproj b/EasyTables.xcodeproj/project.pbxproj index 1e958ea..9679e22 100644 --- a/EasyTables.xcodeproj/project.pbxproj +++ b/EasyTables.xcodeproj/project.pbxproj @@ -7,18 +7,21 @@ objects = { /* Begin PBXBuildFile section */ + 5404033A1F67B669000A7C79 /* ColumnDefinition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 540403391F67B669000A7C79 /* ColumnDefinition.swift */; }; 5415A6AB1F3F002B000FF9BB /* ClosureControls.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5415A6AA1F3F002B000FF9BB /* ClosureControls.framework */; }; 5415A6AE1F3F052A000FF9BB /* EasyTables.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54CEC1C61F21D4DE00FA3BC6 /* EasyTables.framework */; }; 5415A6AF1F3F05D8000FF9BB /* ClosureControls.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5415A6AA1F3F002B000FF9BB /* ClosureControls.framework */; }; 5415A6B11F3F05EB000FF9BB /* ClosureControls.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5415A6AA1F3F002B000FF9BB /* ClosureControls.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5415A6B61F3F0BA4000FF9BB /* EasyTables.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 54CEC1C61F21D4DE00FA3BC6 /* EasyTables.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 5415A6B81F3F0FB5000FF9BB /* EasyTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CEC20D1F21D6D900FA3BC6 /* EasyTable.swift */; }; + 5415A6B81F3F0FB5000FF9BB /* EasyTableSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CEC20D1F21D6D900FA3BC6 /* EasyTableSource.swift */; }; 5415A6BD1F3F2396000FF9BB /* NSTableView+Easy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5415A6BC1F3F2396000FF9BB /* NSTableView+Easy.swift */; }; + 542578E81F6B1BD700FE6DA8 /* Cartography.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 542578E61F6B1BD300FE6DA8 /* Cartography.framework */; }; + 542578E91F6B1CA500FE6DA8 /* Cartography.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 542578E61F6B1BD300FE6DA8 /* Cartography.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 54CEC1D01F21D4DE00FA3BC6 /* EasyTables.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 54CEC1C61F21D4DE00FA3BC6 /* EasyTables.framework */; }; 54CEC1D51F21D4DE00FA3BC6 /* EasyTablesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CEC1D41F21D4DE00FA3BC6 /* EasyTablesTests.swift */; }; 54CEC1D71F21D4DE00FA3BC6 /* EasyTables.h in Headers */ = {isa = PBXBuildFile; fileRef = 54CEC1C91F21D4DE00FA3BC6 /* EasyTables.h */; settings = {ATTRIBUTES = (Public, ); }; }; 54CEC1F31F21D52500FA3BC6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CEC1F21F21D52500FA3BC6 /* AppDelegate.swift */; }; - 54CEC1F51F21D52500FA3BC6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CEC1F41F21D52500FA3BC6 /* ViewController.swift */; }; + 54CEC1F51F21D52500FA3BC6 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CEC1F41F21D52500FA3BC6 /* TableViewController.swift */; }; 54CEC1F71F21D52500FA3BC6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 54CEC1F61F21D52500FA3BC6 /* Assets.xcassets */; }; 54CEC1FA1F21D52500FA3BC6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 54CEC1F81F21D52500FA3BC6 /* Main.storyboard */; }; 54CEC2101F21D85D00FA3BC6 /* GenericTableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54CEC20F1F21D85D00FA3BC6 /* GenericTableDataSource.swift */; }; @@ -41,6 +44,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + 542578E91F6B1CA500FE6DA8 /* Cartography.framework in Embed Frameworks */, 5415A6B61F3F0BA4000FF9BB /* EasyTables.framework in Embed Frameworks */, 5415A6B11F3F05EB000FF9BB /* ClosureControls.framework in Embed Frameworks */, ); @@ -50,8 +54,12 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 540403391F67B669000A7C79 /* ColumnDefinition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColumnDefinition.swift; sourceTree = ""; }; 5415A6AA1F3F002B000FF9BB /* ClosureControls.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ClosureControls.framework; path = Carthage/Build/Mac/ClosureControls.framework; sourceTree = ""; }; 5415A6BC1F3F2396000FF9BB /* NSTableView+Easy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTableView+Easy.swift"; sourceTree = ""; }; + 542578E21F6B18D800FE6DA8 /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile; sourceTree = ""; }; + 542578E31F6B18D800FE6DA8 /* Cartfile.resolved */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile.resolved; sourceTree = ""; }; + 542578E61F6B1BD300FE6DA8 /* Cartography.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cartography.framework; path = Carthage/Build/Mac/Cartography.framework; sourceTree = ""; }; 54CEC1C61F21D4DE00FA3BC6 /* EasyTables.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = EasyTables.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 54CEC1C91F21D4DE00FA3BC6 /* EasyTables.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EasyTables.h; sourceTree = ""; }; 54CEC1CA1F21D4DE00FA3BC6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -60,11 +68,11 @@ 54CEC1D61F21D4DE00FA3BC6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54CEC1F01F21D52500FA3BC6 /* EasyTablesExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EasyTablesExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54CEC1F21F21D52500FA3BC6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 54CEC1F41F21D52500FA3BC6 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 54CEC1F41F21D52500FA3BC6 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 54CEC1F61F21D52500FA3BC6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54CEC1F91F21D52500FA3BC6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 54CEC1FB1F21D52500FA3BC6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 54CEC20D1F21D6D900FA3BC6 /* EasyTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EasyTable.swift; sourceTree = ""; }; + 54CEC20D1F21D6D900FA3BC6 /* EasyTableSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EasyTableSource.swift; sourceTree = ""; }; 54CEC20F1F21D85D00FA3BC6 /* GenericTableDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenericTableDataSource.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -91,6 +99,7 @@ files = ( 5415A6AE1F3F052A000FF9BB /* EasyTables.framework in Frameworks */, 5415A6AF1F3F05D8000FF9BB /* ClosureControls.framework in Frameworks */, + 542578E81F6B1BD700FE6DA8 /* Cartography.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -100,6 +109,7 @@ 5415A6A91F3F0020000FF9BB /* Frameworks */ = { isa = PBXGroup; children = ( + 542578E61F6B1BD300FE6DA8 /* Cartography.framework */, 5415A6AA1F3F002B000FF9BB /* ClosureControls.framework */, ); name = Frameworks; @@ -108,6 +118,8 @@ 54CEC1BC1F21D4DE00FA3BC6 = { isa = PBXGroup; children = ( + 542578E21F6B18D800FE6DA8 /* Cartfile */, + 542578E31F6B18D800FE6DA8 /* Cartfile.resolved */, 5415A6A91F3F0020000FF9BB /* Frameworks */, 54CEC1C81F21D4DE00FA3BC6 /* EasyTables */, 54CEC1D31F21D4DE00FA3BC6 /* EasyTablesTests */, @@ -131,7 +143,8 @@ children = ( 54CEC1C91F21D4DE00FA3BC6 /* EasyTables.h */, 54CEC1CA1F21D4DE00FA3BC6 /* Info.plist */, - 54CEC20D1F21D6D900FA3BC6 /* EasyTable.swift */, + 54CEC20D1F21D6D900FA3BC6 /* EasyTableSource.swift */, + 540403391F67B669000A7C79 /* ColumnDefinition.swift */, 54CEC20F1F21D85D00FA3BC6 /* GenericTableDataSource.swift */, 5415A6BC1F3F2396000FF9BB /* NSTableView+Easy.swift */, ); @@ -151,7 +164,7 @@ isa = PBXGroup; children = ( 54CEC1F21F21D52500FA3BC6 /* AppDelegate.swift */, - 54CEC1F41F21D52500FA3BC6 /* ViewController.swift */, + 54CEC1F41F21D52500FA3BC6 /* TableViewController.swift */, 54CEC1F61F21D52500FA3BC6 /* Assets.xcassets */, 54CEC1F81F21D52500FA3BC6 /* Main.storyboard */, 54CEC1FB1F21D52500FA3BC6 /* Info.plist */, @@ -306,9 +319,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5404033A1F67B669000A7C79 /* ColumnDefinition.swift in Sources */, 5415A6BD1F3F2396000FF9BB /* NSTableView+Easy.swift in Sources */, 54CEC2101F21D85D00FA3BC6 /* GenericTableDataSource.swift in Sources */, - 5415A6B81F3F0FB5000FF9BB /* EasyTable.swift in Sources */, + 5415A6B81F3F0FB5000FF9BB /* EasyTableSource.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -324,7 +338,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 54CEC1F51F21D52500FA3BC6 /* ViewController.swift in Sources */, + 54CEC1F51F21D52500FA3BC6 /* TableViewController.swift in Sources */, 54CEC1F31F21D52500FA3BC6 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/EasyTables/ColumnDefinition.swift b/EasyTables/ColumnDefinition.swift new file mode 100644 index 0000000..4cf7e60 --- /dev/null +++ b/EasyTables/ColumnDefinition.swift @@ -0,0 +1,112 @@ +// +// Copyright (c) 2017 Marco Conti +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + +/// Width of the column +public enum ColumnWidth { + case S + case M + case L + case XL + case custom(size: CGFloat) + + var width: CGFloat { + switch self { + case .S: + return 25 + case .M: + return 150 + case .L: + return 300 + case .XL: + return 500 + case .custom(let size): + return size + } + } +} + +/// Definition of how to display a column in a NSTableView +public struct ColumnDefinition { + + /// Name of the column + let name: String + /// Derive the string to display from the object + let stringToDisplay: (Object)->(String) + /// Comparison operator + let comparison: Comparison + /// Witdh of the column + let width: ColumnWidth + + public init(_ name: String, + width: ColumnWidth = .M, + comparison: Comparison? = nil, + _ stringToDisplay: @escaping (Object)->(String) + ) { + self.name = name + self.stringToDisplay = stringToDisplay + self.width = width + self.comparison = comparison ?? ValueComparison({ obj in + return stringToDisplay(obj) + }) + } +} + +/// Compare two objects +public class Comparison { + + /// Sort two values + public func compare(lhs: Any, rhs: Any) -> ComparisonResult { + return String(describing: lhs).compare(String(describing: rhs)) + } +} + +/// A comparison performed by converting the object into a comparable value +public class ValueComparison: Comparison { + + /// Derive the sorting value from the object + let sortableValue: (Object)->(SortingValue) + + public init(_ sortableValue: @escaping (Object)->(SortingValue)) { + self.sortableValue = sortableValue + } + + override public func compare(lhs: Any, rhs: Any) -> ComparisonResult { + guard let left = lhs as? Object, + let right = rhs as? Object + else { return ComparisonResult.orderedSame } + let leftValue = self.sortableValue(left) + let rightValue = self.sortableValue(right) + + switch (leftValue, rightValue) { + case let (l, r) where l == r: + return ComparisonResult.orderedSame + case let (l, r) where l > r: + return ComparisonResult.orderedDescending + case let (l, r) where l < r: + return ComparisonResult.orderedAscending + default: + return ComparisonResult.orderedSame + } + } +} diff --git a/EasyTables/EasyTable.swift b/EasyTables/EasyTableSource.swift similarity index 89% rename from EasyTables/EasyTable.swift rename to EasyTables/EasyTableSource.swift index dae077b..4cc4f34 100644 --- a/EasyTables/EasyTable.swift +++ b/EasyTables/EasyTableSource.swift @@ -23,43 +23,6 @@ import Cocoa import ClosureControls -public enum ColumnWidth { - case S - case M - case L - case XL - case custom(size: CGFloat) - - var width: CGFloat { - switch self { - case .S: - return 25 - case .M: - return 150 - case .L: - return 300 - case .XL: - return 500 - case .custom(let size): - return size - } - } -} - -/// Definition of how to display a column in a NSTableView -public struct ColumnDefinition { - - let name: String - let stringToDisplay: (Object)->(String) - let width: ColumnWidth - - public init(_ name: String, width: ColumnWidth = .M, _ stringToDisplay: @escaping (Object)->(String)) { - self.name = name - self.stringToDisplay = stringToDisplay - self.width = width - } -} - /// Identifier for a text view cell let TextCellViewIdentifier = "EasyDialogs_TextCellViewIdentifier" diff --git a/EasyTables/GenericTableDataSource.swift b/EasyTables/GenericTableDataSource.swift index 556444f..c5da1d6 100644 --- a/EasyTables/GenericTableDataSource.swift +++ b/EasyTables/GenericTableDataSource.swift @@ -27,6 +27,9 @@ public class GenericTableDataSource: NSObject, NSTableViewDel /// Objects in the table, sorted private(set) var sortedObjects: [Object] = [] + /// Initial objects + private(set) var originalObjects: [Object] = [] + /// Table associated with this data source let table: NSTableView @@ -36,6 +39,13 @@ public class GenericTableDataSource: NSObject, NSTableViewDel /// Called when the selection changes let selectionCallback: ([Object])->(Void) + /// Currently applied filter + public var filter: ((Object)->Bool)? { + didSet { + self.recalculateSource() + } + } + init(initialObjects: [Object], columns: [ColumnDefinition], contextMenuOperations: [ObjectOperation] = [], @@ -43,7 +53,7 @@ public class GenericTableDataSource: NSObject, NSTableViewDel allowMultipleSelection: Bool, selectionCallback: @escaping ([Object])->(Void) = { _ in } ) { - self.sortedObjects = initialObjects + self.filter = nil self.table = table var columnsLookup: [String: ColumnDefinition] = [:] columns.forEach { @@ -52,6 +62,7 @@ public class GenericTableDataSource: NSObject, NSTableViewDel self.columns = columnsLookup self.selectionCallback = selectionCallback super.init() + self.update(newObjects: initialObjects) } public func numberOfRows(in tableView: NSTableView) -> Int { @@ -86,6 +97,7 @@ public class GenericTableDataSource: NSObject, NSTableViewDel public func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) { self.resortItems() + self.table.reloadData() } public func tableViewSelectionDidChange(_ notification: Notification) { @@ -95,16 +107,28 @@ public class GenericTableDataSource: NSObject, NSTableViewDel self.selectionCallback(objects) } + /// Update the objects, re-apply filter and sorting func update(newObjects: [Object]) { - self.sortedObjects = newObjects - self.resortItems() - self.refreshTable() + self.originalObjects = newObjects + self.recalculateSource() } - private func refreshTable() { + /// Refilter original objects then sort them + private func recalculateSource() { + self.filterItems() + self.resortItems() self.table.reloadData() } + /// Filter the sorted objects + private func filterItems() { + if let filter = self.filter { + self.sortedObjects = self.originalObjects.filter(filter) + } else { + self.sortedObjects = self.originalObjects + } + } + /// Returns the object at the given row func value(row: Int) -> Object? { guard row >= 0, row < self.sortedObjects.count else { return nil } @@ -119,9 +143,7 @@ public class GenericTableDataSource: NSObject, NSTableViewDel else { return nil } let ascending = descriptor.ascending return { v1, v2 -> ComparisonResult in - let s1 = column.stringToDisplay(v1).lowercased() - let s2 = column.stringToDisplay(v2).lowercased() - let comparison = s1.compare(s2) + let comparison = column.comparison.compare(lhs: v1, rhs: v2) if ascending { return comparison.inverted } @@ -142,7 +164,6 @@ public class GenericTableDataSource: NSObject, NSTableViewDel } return true } - self.refreshTable() } /// Select the item, if present. This causes a linear scan of the table (`O(n)`). diff --git a/EasyTablesExample/Base.lproj/Main.storyboard b/EasyTablesExample/Base.lproj/Main.storyboard index da558c1..f9cc65d 100644 --- a/EasyTablesExample/Base.lproj/Main.storyboard +++ b/EasyTablesExample/Base.lproj/Main.storyboard @@ -1,7 +1,8 @@ - - + + - + + @@ -653,7 +654,7 @@ - + @@ -676,10 +677,10 @@ - + - + diff --git a/EasyTablesExample/ViewController.swift b/EasyTablesExample/TableViewController.swift similarity index 58% rename from EasyTablesExample/ViewController.swift rename to EasyTablesExample/TableViewController.swift index 1b80bf9..6a0873d 100644 --- a/EasyTablesExample/ViewController.swift +++ b/EasyTablesExample/TableViewController.swift @@ -22,24 +22,49 @@ import Cocoa import EasyTables +import ClosureControls +import Cartography /// An example of how to use `TableConfiguration` -class ViewController: NSViewController { +class TableViewController: NSViewController { - var tableSource: EasyTableSource! + private var tableSource: EasyTableSource! - var objects = Set(["Action", "Engineering", "Cod", "Doodle"]) + private static let fishes = ["Cod", "Shark"] + private static let items = ["Hammer", "Doodle", "Speaker"] + private var objects = Set(fishes + items) override func viewDidLoad() { super.viewDidLoad() - let (scroll, table) = NSTableView.inScrollView() - self.view.addSubview(scroll) + let selectButton = ClosureButton(label: "Select all Fish") { _ in + self.tableSource.dataSource.select(items: TableViewController.fishes) + } + + let filterButton = ClosureButton(label: "Filter out non-fish") { btn in + guard let button = btn as? NSButton else { return } + switch button.state { + case NSOnState: + self.tableSource.dataSource.filter = { TableViewController.fishes.contains($0) } + case NSOffState: + self.tableSource.dataSource.filter = nil + default: + return + } + } + filterButton.setButtonType(.switch) - scroll.createConstraintsToFillParent(self.view) + let (scroll, table) = NSTableView.inScrollView() + self.createLayoutConstraints(table: scroll, button1: selectButton, button2: filterButton) + self.createTableSource(for: table) + + } + + private func createTableSource(for table: NSTableView) { + self.tableSource = EasyTableSource( - initialObjects: objects, + initialObjects: self.objects, columns: [ ColumnDefinition("Word", { $0 }), ColumnDefinition("Length", { "\($0.characters.count)" }), @@ -74,7 +99,28 @@ class ViewController: NSViewController { }) } - override func keyUp(with event: NSEvent) { - self.tableSource.dataSource.select(items: ["Cod", "Action"]) + private func createLayoutConstraints(table: NSView, button1: NSView, button2: NSView) { + self.view.addSubview(table) + self.view.addSubview(button1) + self.view.addSubview(button2) + + let space: CGFloat = 10 + constrain(table, button1, button2, self.view) { table, b1, b2, frame in + table.bottom == frame.bottom - space + table.left == frame.left + space + table.right == frame.right - space + + b1.leading == frame.leading + space + b1.trailing == b2.leading - space + b2.trailing == frame.trailing - space + b1.top == b2.top + b1.top == frame.top + space + b1.bottom == b2.bottom + b1.width == b2.width + + table.top == b1.bottom + space + } } + } +