Skip to content

Commit

Permalink
Add table filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
marcoconti83 committed Sep 14, 2017
1 parent 83a9568 commit de03051
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 69 deletions.
1 change: 1 addition & 0 deletions Cartfile.private
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github "robb/Cartography" "1.1.0"
1 change: 1 addition & 0 deletions Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
github "marcoconti83/ClosureControls" "v0.2.1"
github "robb/Cartography" "1.1.0"
30 changes: 22 additions & 8 deletions EasyTables.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand All @@ -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 */,
);
Expand All @@ -50,8 +54,12 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
540403391F67B669000A7C79 /* ColumnDefinition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColumnDefinition.swift; sourceTree = "<group>"; };
5415A6AA1F3F002B000FF9BB /* ClosureControls.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ClosureControls.framework; path = Carthage/Build/Mac/ClosureControls.framework; sourceTree = "<group>"; };
5415A6BC1F3F2396000FF9BB /* NSTableView+Easy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTableView+Easy.swift"; sourceTree = "<group>"; };
542578E21F6B18D800FE6DA8 /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile; sourceTree = "<group>"; };
542578E31F6B18D800FE6DA8 /* Cartfile.resolved */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile.resolved; sourceTree = "<group>"; };
542578E61F6B1BD300FE6DA8 /* Cartography.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cartography.framework; path = Carthage/Build/Mac/Cartography.framework; sourceTree = "<group>"; };
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 = "<group>"; };
54CEC1CA1F21D4DE00FA3BC6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand All @@ -60,11 +68,11 @@
54CEC1D61F21D4DE00FA3BC6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>"; };
54CEC1F41F21D52500FA3BC6 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
54CEC1F41F21D52500FA3BC6 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = "<group>"; };
54CEC1F61F21D52500FA3BC6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
54CEC1F91F21D52500FA3BC6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
54CEC1FB1F21D52500FA3BC6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
54CEC20D1F21D6D900FA3BC6 /* EasyTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EasyTable.swift; sourceTree = "<group>"; };
54CEC20D1F21D6D900FA3BC6 /* EasyTableSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EasyTableSource.swift; sourceTree = "<group>"; };
54CEC20F1F21D85D00FA3BC6 /* GenericTableDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenericTableDataSource.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand All @@ -91,6 +99,7 @@
files = (
5415A6AE1F3F052A000FF9BB /* EasyTables.framework in Frameworks */,
5415A6AF1F3F05D8000FF9BB /* ClosureControls.framework in Frameworks */,
542578E81F6B1BD700FE6DA8 /* Cartography.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -100,6 +109,7 @@
5415A6A91F3F0020000FF9BB /* Frameworks */ = {
isa = PBXGroup;
children = (
542578E61F6B1BD300FE6DA8 /* Cartography.framework */,
5415A6AA1F3F002B000FF9BB /* ClosureControls.framework */,
);
name = Frameworks;
Expand All @@ -108,6 +118,8 @@
54CEC1BC1F21D4DE00FA3BC6 = {
isa = PBXGroup;
children = (
542578E21F6B18D800FE6DA8 /* Cartfile */,
542578E31F6B18D800FE6DA8 /* Cartfile.resolved */,
5415A6A91F3F0020000FF9BB /* Frameworks */,
54CEC1C81F21D4DE00FA3BC6 /* EasyTables */,
54CEC1D31F21D4DE00FA3BC6 /* EasyTablesTests */,
Expand All @@ -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 */,
);
Expand All @@ -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 */,
Expand Down Expand Up @@ -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;
};
Expand All @@ -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;
Expand Down
112 changes: 112 additions & 0 deletions EasyTables/ColumnDefinition.swift
Original file line number Diff line number Diff line change
@@ -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<Object> {

/// Name of the column
let name: String
/// Derive the string to display from the object
let stringToDisplay: (Object)->(String)
/// Comparison operator
let comparison: Comparison<Object>
/// Witdh of the column
let width: ColumnWidth

public init(_ name: String,
width: ColumnWidth = .M,
comparison: Comparison<Object>? = 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<Object> {

/// 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<Object, SortingValue: Comparable>: Comparison<Object> {

/// 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
}
}
}
37 changes: 0 additions & 37 deletions EasyTables/EasyTable.swift → EasyTables/EasyTableSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object> {

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"

Expand Down
39 changes: 30 additions & 9 deletions EasyTables/GenericTableDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public class GenericTableDataSource<Object: Equatable>: 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

Expand All @@ -36,14 +39,21 @@ public class GenericTableDataSource<Object: Equatable>: 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<Object>],
contextMenuOperations: [ObjectOperation<Object>] = [],
table: NSTableView,
allowMultipleSelection: Bool,
selectionCallback: @escaping ([Object])->(Void) = { _ in }
) {
self.sortedObjects = initialObjects
self.filter = nil
self.table = table
var columnsLookup: [String: ColumnDefinition<Object>] = [:]
columns.forEach {
Expand All @@ -52,6 +62,7 @@ public class GenericTableDataSource<Object: Equatable>: NSObject, NSTableViewDel
self.columns = columnsLookup
self.selectionCallback = selectionCallback
super.init()
self.update(newObjects: initialObjects)
}

public func numberOfRows(in tableView: NSTableView) -> Int {
Expand Down Expand Up @@ -86,6 +97,7 @@ public class GenericTableDataSource<Object: Equatable>: NSObject, NSTableViewDel

public func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
self.resortItems()
self.table.reloadData()
}

public func tableViewSelectionDidChange(_ notification: Notification) {
Expand All @@ -95,16 +107,28 @@ public class GenericTableDataSource<Object: Equatable>: 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 }
Expand All @@ -119,9 +143,7 @@ public class GenericTableDataSource<Object: Equatable>: 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
}
Expand All @@ -142,7 +164,6 @@ public class GenericTableDataSource<Object: Equatable>: NSObject, NSTableViewDel
}
return true
}
self.refreshTable()
}

/// Select the item, if present. This causes a linear scan of the table (`O(n)`).
Expand Down
Loading

0 comments on commit de03051

Please sign in to comment.