How to add an image to a NSOutlineView - swift

I'm trying to add an image to a cell in a NSOutlineView :
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
df.locale = Locale.current
df.setLocalizedDateFormatFromTemplate("YYYY-MM-dd")
if ( df.locale.identifier == "fr_FR" ) {df.dateFormat="dd-MM-YYYY"}
if ( myView == 0 ) {
if let c = item as? Competition {
if tableColumn?.identifier == "col1" {
if let view = outlineView.make(withIdentifier: "column1", owner: self) as? NSTableCellView{
if let viewTextField = view.textField{
viewTextField.stringValue = df.string(from: c.date)
}
view.imageView?.image = NSImage(named: "NSSlideshowTemplate")
return view;
}
}
}
}
return nil
}
But it's not working.
I have no problem with a NSTableView ..
Any idea why ?
Thanks,
Nicolas

Related

NSOutlineView spacing between items is incorrect

I have an NSOutlineView, and the space between items 1 and 2 is greater than the spacing between all other items. These are group items. The outline view is very bare bones. Below is my code, and below that is an image of the issue I am referring to:
var sections: [SidebarSection] = [
SidebarSection(title: "Section 1", items: ["1", "2", "3", "4", "5"]),
SidebarSection(title: "Section 2", items: ["1", "2"]),
SidebarSection(title: "Section 3", items: ["1", "2"]),
SidebarSection(title: "Section 4", items: ["1", "2"]),
SidebarSection(title: "Section 5", items: ["1", "2"])]
private let outlineView: SidebarOutlineView = SidebarOutlineView()
extension SidebarViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return item is SidebarSection
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let section = item as? SidebarSection {
return section.items[index]
}
return sections[index]
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let section = item as? SidebarSection {
return section.items.count
}
return sections.count
}
}
extension SidebarViewController: NSOutlineViewDelegate {
func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
return 30
}
func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool {
return item is SidebarSection
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "SidebarPlaylistCell"), owner: self) as! SidebarPlaylistCell
view.wantsLayer = true
view.textLabel.stringValue = (item as? SidebarSection)?.title ?? ""
view.imgView.wantsLayer = true
view.imgView.layer?.backgroundColor = NSColor.red.cgColor
return view
}
}
extension SidebarViewController {
func layout() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.documentView = outlineView
outlineView.headerView = nil
outlineView.delegate = self
outlineView.dataSource = self
outlineView.intercellSpacing = NSSize(width: 0, height: 0)
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalScroller = false
scrollView.wantsLayer = true
view.layer?.backgroundColor = NSColor.blue.cgColor
}
}
final class SidebarOutlineView: NSOutlineView {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
let column: NSTableColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("col1"))
column.isEditable = false
column.minWidth = 0
addTableColumn(column)
outlineTableColumn = column
register(NSNib(nibNamed: "SidebarPlaylistCell", bundle: nil), forIdentifier: NSUserInterfaceItemIdentifier(rawValue: "SidebarPlaylistCell"))
selectionHighlightStyle = .sourceList
indentationPerLevel = 6.0
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
This behavior can be controlled by a boolean flag floatsGroupRows, which is set to true by default. So by adding:
outlineView.floatsGroupRows = false
you get the desired behavior.
Documentation can be found here:
https://developer.apple.com/documentation/appkit/nstableview/1528624-floatsgrouprows?language=swift
A Boolean value indicating whether the table view draws grouped rows as if they are floating.
Quick Test
Meaning of floatGroupRows
The effect of floatsGroupRows can be seen better in an animated graphic. During a scroll operation, the current GroupItem remains floating on top:

NSOutlineView unexpectedly found nil

I am trying to make a file browser in my app that opens in a side panel (with a split view controller).
The source is a URL brought by a prepareForSegue method in the previous viewController.
Each time the vc loads i have the fatal error :
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
The compiler locates the error to where i declare :
outlineView.delegate = self
outlineView.dataSource = self
I tried :
1. Undoing and redoing all my outlets connections, by code, by
storyboard
2. Reconnecting delegates and datasource by code, by storyboard
3. I thought maybe something was wrong in my datasource method and i rewrote it 5 times
4. I tried to put my setDelegatesAndDatasource method in the viewDidAppear too, thinking it was a problem of view life cycle
I can't understand what's going on.
Thanks for your help.
'''
extension ViewControllerSource : NSOutlineViewDataSource, NSOutlineViewDelegate {
func setDelegatesAndDatasources(){
outlineView.delegate = self
outlineView.dataSource = self
}
// MARK: - NSOutlineView Datasource
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let fileSystemItem = item as? FileSystemItem {
return fileSystemItem.children.count
}
return 1
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let fileSystemItem = item as? FileSystemItem {
return fileSystemItem.children[index]
}
return rootfileSystemItem
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
if let fileSystemItem = item as? FileSystemItem {
return fileSystemItem.hasChildren()
}
return false
}
// MARK: - NSOutlineView Delegate
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
guard let colIdentifier = tableColumn?.identifier else { return nil }
if colIdentifier == NSUserInterfaceItemIdentifier(rawValue: "col1") {
let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "cell1")
guard let cell = outlineView.makeView(withIdentifier: cellIdentifier, owner: nil) as? NSTableCellView else { return nil }
if let collection = item as? FileSystemItem {
cell.textField?.stringValue = collection.name ?? "Title not available"
cell.textField?.isEditable = false
cell.textField?.wantsLayer = true
cell.imageView?.image = collection.icon
// cell.textField?.delegate = self
} else {
cell.textField?.stringValue = "unknown item"
cell.textField?.isEditable = false
cell.textField?.wantsLayer = true
}
return cell
} else {
return nil
}
}
}
'''
And here is the main viewController file :
'''
class ViewControllerSource: NSViewController {
#IBOutlet var outlineView: NSOutlineView!
var echo:Echo? {
didSet {
echo!.checkFolderIntegrity()
rootfileSystemItem = FileSystemItem(url: echo!.url)
let window = self.view.window?.windowController as! WindowControllerEcho
window.directoryPath.url = echo!.url
}
}
let propertyKeys: [URLResourceKey] = [.localizedNameKey, .effectiveIconKey, .isDirectoryKey, .typeIdentifierKey]
var rootfileSystemItem: FileSystemItem! {
didSet {
displayItems()
outlineView.reloadData()
}
}
// MARK: - Initialization
override func viewDidLoad() {
super.viewDidLoad()
setDelegatesAndDatasources()
}
func displayItems(){
for fileSystemItem in rootfileSystemItem.children as [FileSystemItem] {
print("item : \(fileSystemItem)")
for subItem in fileSystemItem.children as [FileSystemItem] {
print("\(fileSystemItem.name) - \(subItem.name)")
}
}
}
}
extension ViewControllerSource : EchoDelegate {
func didLoad(echo: Echo) {
self.echo = echo
}
}
'''
The prepareForSegue code reveals the mistake:
You are setting echo in prepareForSegue. This causes to call the property observer didSet. However at this moment the view is not loaded yet and force unwrapping the type crashes.
The solution is to move the code in didSet into viewDidLoad and viewWillAppear and delete the property observer. Nevertheless I recommend to optional bind window
var echo : Echo!
override func viewDidLoad() {
super.viewDidLoad()
setDelegatesAndDatasources()
echo.checkFolderIntegrity()
rootfileSystemItem = FileSystemItem(url: echo.url)
}
override func viewWillAppear(_ animated : Bool) {
super.viewWillAppear(animated)
if let window = self.view.window?.windowController as? WindowControllerEcho {
window.directoryPath.url = echo.url
}
}
Setting delegate and dataSource once is sufficient. If you are using storyboard or Xib the most convenient way is to connect both in Interface Builder.

NSTableView not updating on initial loading

I have an NSView with a NSTableView called personTableView. In the ViewController class, I have the following code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
personTableView.delegate = self
personTableView.dataSource = self
personTableView.reloadData()
}
and have extended the class to with NSTableViewDelegate and NSTableViewDataSource
However, when the view appears, the table shows the following (there are only 2 entries that the table should display):
On my window, I have a button which invokes the following action:
#IBAction func refreshButton(_ sender: NSButton) {
let result = CoreDataHandler.fetchCount()
print("Row Count:\(result)")
personTableView.reloadData()
codeTableView.reloadData()
}
which when pressed, populates my TableView. I don't understand why it won't load automatically?
I have also tried putting the personTableView.reloadData() into viewWillAppear and viewDidAppear to no avail.
Update:
This is the fetchCount():
static func fetchCount() -> Int {
let context = getContext()
do {
let count = try context.count(for: Person.fetchRequest())
NSLog("Count from fetchCount: %d", count)
return count
} catch {
return 0
}
}
For information, this is the Table Delegate and DataSource functions:
extension ViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
if tableView == self.personTableView {
let result = CoreDataHandler.fetchCount()
//NSLog("Rows in Ext: %#",result)
return result
}
if tableView == self.codeTableView {
let row = personTableView.selectedRow
if row > -1 {
let person = CoreDataHandler.fetchPerson()?[row]
print("Person= \(String(describing: person?.first))")
//let result = CoreDataHandler.fetchCodes(person: person!)
let result = person?.codes
//print("Person from result: \(String(describing: result?.first.whosAccount?.ibAccount))")
let count = result!.count
print("Rows in Codes from viewController dataSource: \(count)")
return count
} else {
return 0
}
}
return 0
}
}
extension ViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
if tableView == self.personTableView {
guard let person = CoreDataHandler.fetchPerson()?[row] else {
return nil
}
if let cell = tableView.makeView(withIdentifier: (tableColumn!.identifier), owner: nil) as? NSTableCellView {
if tableColumn == tableView.tableColumns[0] {
cell.textField?.stringValue = (person.first ?? nil) ?? ""
} else if tableColumn == tableView.tableColumns[1] {
cell.textField?.stringValue = (person.last ?? nil) ?? ""
} else {
cell.textField?.stringValue = (person.ibAccount ?? nil) ?? ""
}
return cell
} else {
return nil
}
}
if tableView == self.codeTableView {
let personRow = personTableView.selectedRow
if personRow > -1 {
let person = CoreDataHandler.fetchPerson()?[personRow]
guard let code = CoreDataHandler.fetchCodes(person: person!)?[row] else {
return nil
}
if let cell = tableView.makeView(withIdentifier: (tableColumn!.identifier), owner: nil) as? NSTableCellView {
if tableColumn == tableView.tableColumns[0] {
cell.textField?.stringValue = (String(code.number) )
//cell.textField?.stringValue = person?.codes?.allObjects[row] as! String
} else if tableColumn == tableView.tableColumns[1] {
cell.textField?.stringValue = code.code!
} else if tableColumn == tableView.tableColumns[2] {
cell.textField?.stringValue = (code.whosAccount?.ibAccount ?? "")
}
return cell
} else {
return nil
}
}
}
return nil
}
}
You've mixed up tableView(_:objectValueFor:row:) of NSTableViewDataSource and tableView(_:viewFor:row:) of NSTableViewDelegate. Replace
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any?
by
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
or return strings from tableView(_:objectValueFor:row:)
It seems like the table view is being populated based on data stored in the result variable. Try making the call to let result = CoreDataHandler.fetchCount() in viewDidLoad

NSOutlineView, how to get the selected cell

I want to get the selected cell from the NSOutlineView control. I found a solution here: How can I get the selected cell from a NSOutlineView?. It says
Use the delegate. willDisplayCell: is called when a cell changes its selection state.
However when I test it, I found that my willDisplayCell: is not be called.
Here's my code, it can be run normally, but the willDisplayCell: method has never been called. Where did I make a mistake? Thanks.
class TreeNode: NSObject{
var name: String = ""
private(set) var isLeaf: Bool = false
var children: [TreeNode]?
init(name: String, isLeaf: Bool){
self.name = name
self.isLeaf = isLeaf
if !isLeaf{
children = [TreeNode]()
}
}
}
class ViewController: NSViewController {
#IBOutlet weak var sourceList: NSOutlineView!
private var data = TreeNode(name: "Root", isLeaf: false)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
for i in 0..<10{
let node = TreeNode(name: "name \(i)", isLeaf: i % 2 == 0)
data.children?.append(node)
}
}
}
extension ViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let item = item as? TreeNode, !item.isLeaf {
return item.children!.count
}
return 1
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return !((item as? TreeNode)?.isLeaf ?? false)
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let item = item as? TreeNode {
if item.isLeaf{
return item
}else{
return item.children![index]
}
}
return data
}
}
extension ViewController: NSOutlineViewDelegate {
func outlineView(_ outlineView: NSOutlineView, willDisplayCell cell: Any, for tableColumn: NSTableColumn?, item: Any) {
print("called")
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let cell: NSTableCellView?
if let item = item as? TreeNode{
cell = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DataCell"), owner: self) as? NSTableCellView
cell?.textField?.stringValue = item.name
}else{
cell = nil
}
return cell
}
}
I've solved this problem myself. The following code is how to get the selected cell:
func getSelectedCell() -> NSTableCellView? {
if let view = self.sourceList.rowView(atRow: self.sourceList.selectedRow, makeIfNecessary: false) {
return view.view(atColumn: self.sourceList.selectedColumn) as? NSTableCellView
}
return nil
}
Now I can access the NSTextField control by the code getSelectedCell()?.textField.

Columns added programmatically to NSTableView not recognised in Delegate

I may be lost in a glass of water but I can't seem to be able to add columns to a NSTableView that are then recognised in the NSTableViewDelegate. I create a table in IB with one column and give the column a string identifier. The I add the other columns in the View Controller:
override func viewDidLoad() {
super.viewDidLoad()
for columnIndex in 0..<blotter!.singleOutput[0].parameter.count {
let tmpParam = blotter!.singleOutput[0].parameter[columnIndex]
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: tmpParam.columnID))
column.title = tmpParam.label
column.width = CGFloat(80)
column.minWidth = CGFloat(40)
column.maxWidth = CGFloat(120)
blotterOutputTable.addTableColumn(column)
}
blotterOutputTable.delegate = self
blotterOutputTable.dataSource = self
blotterOutputTable.target = self
blotterOutputTable.reloadData()
}
The NSTableViewDataSource returns the correct number of rows. The problem I have is in the NSTableViewDelegate:
extension OutputsViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
var text: String = ""
var cellIdentifier: String = ""
guard let item = blotter?.singleOutput[row] else { return nil }
// 1. LABELS COLUMN
// ================
if tableColumn?.identifier.rawValue == "dealColumn" {
let myParameter = item.parameter.index(where: {$0.columnID == "BBTickColumn"})
text = item.parameter[myParameter!].value as! String
cellIdentifier = "dealColumn"
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier), owner: nil) as? NSTableCellView {
cell.textField?.stringValue = text
return cell
}
else { return nil }
} // END OF LABLES COLUMN (FIRST ONE)
else { // THIS IS WHERE THE PROBLEM IS
let myParameter = item.parameter.index(where: {$0.columnID == tableColumn?.identifier.rawValue } )
let (_, valueAsText) = item.parameter[myParameter!].interfaceItems()
text = valueAsText
cellIdentifier = item.parameter[myParameter!].columnID
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier), owner: nil) as? NSTableCellView {
cell.textField?.stringValue = text
return cell
}
else { return nil } // DEBUGGER PARAMETER ARE FROM HERE
}
}
}
The first column is the one I created in IB with its identifier. That works. The problem I have is in the else statement (which does not check for a column identifier). Below are the parameters as I see them in the debugger window when I stop the program after the cell creation failed
tableView NSTableView 0x000000010ebf9df0
tableColumn NSTableColumn? 0x0000600000895770
row Int 0
self DataBaseManager.OutputsViewController 0x0000600000102b50
text String "FLAT"
cellIdentifier String "directionColumn"
item DataBaseManager.BlotterOutputs 0x000060000002c240
myParameter Array.Index? 0
valueAsText String "FLAT"
cell (null) (null) (null)
tableColumn NSTableColumn? 0x0000600000895770
tableColumn?.identifier NSUserInterfaceItemIdentifier? some
_rawValue _NSContiguousString "directionColumn" 0x000060000104d200
Swift._SwiftNativeNSString _SwiftNativeNSString
_core _StringCore
You can see that cellIdentifier and the tableColumn?.identifier.rawvalue are the same string (as it should be). I cannot understand then why the cell is not created. Any help is mostly welcome and let me know if this is not clear. Thanks
must register nibs identifiers as in this code:
import Cocoa
class MultiColumnTable: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
var list = [[String]](), header=[String]()
var tableView : NSTableView? = nil
var nColumns : Int = 0
func genID(col : Int) -> NSUserInterfaceItemIdentifier { // generate column ID
return NSUserInterfaceItemIdentifier(rawValue: String(format: "Col%d", col))
}
func setContent(header: [String], list : [[String]]) {
self.header = header
self.list = list
self.nColumns = list[0].count
if tableView != nil {
tableView?.reloadData()
}
}
func numberOfRows(in tableView: NSTableView) -> Int {
func createColumns() {
func addColumn(col:Int, header:String) {
let tableColumn = NSTableColumn(identifier: genID(col: col))
tableColumn.headerCell.title = header
self.tableView!.addTableColumn(tableColumn)
}
// create columns and register them in NIB
// IB: tableColumn[0] identifier ( NSTableColumn to "Col0" )
if let myCellViewNib = tableView.registeredNibsByIdentifier![NSUserInterfaceItemIdentifier(rawValue: "Col0")] {
for col in 0..<nColumns { // table should have 1 col in IB w/Ident 'Col0'
addColumn(col: col, header: header[col])
tableView.register(myCellViewNib, forIdentifier: genID(col: col)) // register the above Nib for the newly added tableColumn
}
tableView.removeTableColumn(tableView.tableColumns[0]) // remove the original Col0
}
}
self.tableView = tableView
createColumns()
return list.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let column = tableView.tableColumns.firstIndex(of: tableColumn!)!
tableColumn?.headerCell.title=header[column];
if let cell = tableView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: self) as? NSTableCellView {
cell.textField?.stringValue = list[row][column]
cell.textField?.textColor = NSColor.blue
return cell
}
return nil
}
func tableViewSelectionDidChange(_ notification: Notification) {
}
}