I've got a simple NSOutlineView setup in my OSX Swift project feeding from a basic array but its causing an EXC_BAD_ACCESS crash.
Having enabled zombies, it crashes with the following error:
[NSMutableIndexSet retain]: message sent to deallocated instance
Heeeelp! Here is my code:
class SidebarViewController: NSViewController, NSOutlineViewDataSource {
//MARK: Vars
#IBOutlet var sidebar : NSOutlineView?
var data : [String] = ["Assemblies", "Parts", "Customers"]
//MARK: Init
override func viewDidLoad()
{
super.viewDidLoad()
// Do view setup here.
}
//MARK: NSOutlineView Delegate / Datasource
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int
{
return data.count
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool
{
return false
}
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject
{
return data[index]
}
func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject?
{
return item
}
}
Turns out that objectValueForTableColumn method require an obj_c object returned. So returning String in my array doesn't work. I changed this to NSString and it now works.
Related
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.
I am trying to reload a table view with the help of a delegate. I found tons of examples here on stack overflow, but I always end up with an error.
My first controller which should update the table view:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var tableView: NSTableView!
var tableViewData: [[String:String]] = []
override func viewDidLoad() {
super.viewDidLoad()
...
self.tableView.delegate = self as NSTableViewDelegate
self.tableView.dataSource = self
self.tableView.reloadData()
}
override var representedObject: Any? {
didSet {
}
}
func reloadTableData(_ notification: Notification) {
tableView.reloadData()
}
}
extension ViewController: NSTableViewDataSource, NSTableViewDelegate {
func numberOfRows(in tableView: NSTableView) -> Int {
return tableViewData.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?{
var result:CustomTableCellview
result = tableView.make(withIdentifier: (tableColumn?.identifier)!, owner: self) as! CustomTableCellview
result.textField?.stringValue = tableViewData[row][(result.textField?.identifier!)!]!
result.secondTextField?.stringValue = tableViewData[row][result.secondTextField.identifier!]!
return result
}
}
extension ViewController: PageControllerDelegate {
func updateTableData() {
tableView.reloadData()
}
}
My second controller, which should tell the first one it can update the table view:
import Cocoa
protocol PageControllerDelegate {
func updateTableData()
}
class PageController: NSPageController {
var delegate: PageControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func saveData(sender: NSButton) {
...
delegate?.updateTableData()
self.dismiss(self)
}
}
Within the PageController I get the following error:
Property 'delegate' with type 'PageControllerDelegate?' cannot override a property with type 'NSPageControllerDelegate?' (aka 'Optional<NSPageControllerDelegate>')
Rename the delegate protocol name to something like MyPageControllerDelegate. It seems like there is already something called PageControllerDelegate that is defined either by you, or Apple
NSPageController already has a property delegate of type NSPageControllerDelegate. Remove var delegate: PageControllerDelegate?.
I'd like a correct behaviour from the blue drag bar, and no blue rect when dragging.
Do you know where is my mistake ?
(as you can see, the blue bar is stuck in top, like in this topic :
Little circle-line bar stuck at top of NSOutlineView when rearranging using drag and drop)
import Cocoa
class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate, NSPasteboardItemDataProvider {
#IBOutlet weak var outlineView: NSOutlineView!
let REORDER_PASTEBOARD_TYPE = "com.test.calques.item"
override func viewDidLoad() {
super.viewDidLoad()
//Register for the dropped object types we can accept.
outlineView.register(forDraggedTypes: [REORDER_PASTEBOARD_TYPE])
//Disable dragging items from our view to other applications.
outlineView.setDraggingSourceOperationMask(NSDragOperation(), forLocal: false)
//Enable dragging items within and into our view.
outlineView.setDraggingSourceOperationMask(NSDragOperation.every, forLocal: true)
outlineView.delegate = self;
outlineView.dataSource = self;
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
var items: [(String, NSColor)] = [
("Item 1", NSColor.black),
("Item 2", NSColor.red),
("Item 3", NSColor.red),
("Item 4", NSColor.red),
("Item 5", NSColor.red),
("Item 6", NSColor.red)];
//NSOutlineViewDataSource
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
return items.count;
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return false
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
return items[index];
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let image: NSImage = NSImage(size: NSSize(width: 17, height: 17));
let calquesItem: (String, NSColor) = item as! (String, NSColor);
let path = NSBezierPath(ovalIn: CGRect(x: 2, y: 2, width: 17 - 4, height: 17 - 4));
image.lockFocus();
calquesItem.1.setFill();
path.fill();
image.unlockFocus();
let cell = outlineView.make(withIdentifier: "DataCell", owner: nil) as! NSTableCellView;
cell.textField!.stringValue = calquesItem.0;
cell.imageView!.image = image;
return cell;
}
//Drag - NSOutlineViewDataSource
var fromIndex: Int? = nil;
func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
let pastBoardItem: NSPasteboardItem = NSPasteboardItem();
pastBoardItem.setDataProvider(self, forTypes: [REORDER_PASTEBOARD_TYPE]);
return pastBoardItem;
}
func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) {
Swift.print("willBeginAt")
let item = draggedItems[0] as! (String, NSColor);
fromIndex = items.index(where: { (_item: (String, NSColor)) -> Bool in
return _item.0 == item.0
});
session.draggingPasteboard.setData(Data(), forType: REORDER_PASTEBOARD_TYPE)
}
func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
Swift.print("acceptDrop")
if(fromIndex! != index && index != -1) {
let toIndex: Int = fromIndex! < index ? index - 1 : index;
outlineView.moveItem(at: fromIndex!, inParent: nil, to: toIndex, inParent: nil);
items.insert(items.remove(at: fromIndex!), at: toIndex);
return true;
}
return false;
}
func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
if(item == nil) {
return NSDragOperation.generic;
}
return [];
}
func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
Swift.print("Drag session ended")
fromIndex = nil;
}
//NSPasteboardItemDataProvider
func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: String)
{
item.setString("Outline Pasteboard Item", forType: type)
}
}
From https://developer.apple.com/reference/appkit/nsoutlineview :
Each item in the outline view must be unique. In order for the
collapsed state to remain consistent between reloads the item's
pointer must remain the same and the item must maintain isEqual(_:)
sameness.
I was using Tuples (String, NSColor). And Tuples don't conform to the hashable protocol !
After switching from Tuples items to MyClass items, everything works fine !
(correct behaviour from the blue drag bar, and no blue rect when dragging)
class MyClass {
var name: String!
var color: NSColor!
init(_ _name: String, _ _color: NSColor) {
name = _name
color = _color
}
}
When a minimal OSX application that has nothing else but a plain (unconfigured) NSOutlineView in its storyboard, it will heavily leak memory.
View controller implementation
The view controller is implemented as following:
import Cocoa
class Section {
let title: String
let items: [String]
init(title: String, items: [String]) {
self.title = title
self.items = items
}
}
class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {
//MARK: NSOutlineViewDataSource
let items = [
Section(title: "Alpha", items: ["Anton", "Anita"]),
Section(title: "Beta", items: ["Bernd", "Barbara"]),
Section(title: "Gamma", items: ["Gustav", "Gesine"])]
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
switch item {
case nil: return items.count
case is Section: return (item as! Section).items.count
default: return 0
}
}
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
return item == nil
? items[index]
: (item as! Section).items[index]
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
return item is Section
}
func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {
return item
}
//MARK: NSOutlineViewDelegate
func outlineView(outlineView: NSOutlineView, viewForTableColumn tableColumn: NSTableColumn?, item: AnyObject) -> NSView? {
let view = outlineView.makeViewWithIdentifier(item is Section ? "HeaderCell" : "DataCell", owner: self) as! NSTableCellView
view.textField?.stringValue = item is Section
? (item as! Section).title
: item as! String
return view
}
}
The Xcode project can be cloned from github
Xcode showing a constantly increasing memory consumption
When the app is started and tree items are selected by clicking them, the memory consumption increases tremendously.
Xcode's debug navigator shows an increasing memory consumption over a short time. Memory increases only when you navigate through the NSOutlineView nodes.
Instruments not showing something suspicious (or does it?)
Instruments however is showing a constant memory usage, that is neither increasing nor decreasing:
What can I conclude from Instrument's report? Is it not detecting leaks because it's NSOutlineView itself whose leaking (and those can't be detected?) or is there simply no leak at the end?
I am attempting to implement a simple sourceView, however, I am only managing to set the stringValue of one textField.
First, this is the structure of my program:
class MainWindowController: NSWindowController, NSOutlineViewDataSource, NSOutlineViewDelegate
{
#IBOutlet weak var sourceView: NSOutlineView!
override func windowDidLoad()
{
sourceView.setDataSource(self)
sourceView.setDelegate(self)
}
}
Then, I have:
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int
{...}
And finally,:
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject
{...}
My issue is that in both of these functions, the value of item is nil. Is this what is supposed to happen?
It doesn't appear you're providing the object value. Unless you're using an NSTreeController to populate your NSOutlineView, you must also implement the data source function:
func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {
// return your object value
}