NSOutlineView EXC_BAD_ACCESS crash on expand - swift

I am trying to use an NSOutlineView in my project, using Swift.
I have done this a few times already using Objective-C without any problem, but for some reason now my app keeps crashing with EXC_BAD_ACCESS, mostly when trying to expand a cell.
I have created a new test project with only an outline view and the 4 data source methods, but the crashes happen, there, too.
Here is the minimal implementation:
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
return 3
}
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
return "Test"
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
return true
}
func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {
return nil
}
Any idea of what I am doing wrong?
I have uploaded the test project here. Please review it. https://drive.google.com/file/d/0BzEhecUbyNeFS3JGN1V0SlJ0dWM/view

There are a few things I had to fix to make your sample work as expected:
There's a crash related to "Test" being a local string and released before the outline view tries to retain it. This is fixed by having the items owned by the view controller.
After that, I encountered a crash due to infinite recursion. That was fixed by having a data model instead of telling the outline view that every item had 3 children no matter what level the item was at.
I also changed the items to be instances subclassed from NSObject rather than String because I remember reading that that was necessary. (I currently can't find the reference.)

Related

How to debug in acceptDrop method of Outline View in swift MAC OS?

I'm working on the outline view in mac OS swift. I want to debug in this method when an item is dropped on it. But, the app got stuck when I added a breakpoint in this method.
I have tried debugging using log also, but is there any other way to do so?
Here is the method I'm working on.
func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
}

Safari app extension popover not calling table view notification methods

I want to include a view-based NSTableView in the popover of a Safari App Extension.
Starting with the default project in Xcode, I made the SFSafariExtensionViewController the delegate and datasource for the table view as it is the only content on the popover, and mostly this works.
I can populate the table and implement methods like tableView(_:shouldSelectRow:), yet methods which return a notification object such as tableViewSelectionDidChange(_:) do not get called.
Whilst those methods show a cludgy way of knowing when a row is selected, I am left with no way of knowing when a cell is edited.
As I had to connect the delegate outlet of the NSTableView to the File Owner to allow the delegated methods to work, I also tried connecting the dataSource outlet too, but this rightly did not help.
Here is the essence of my code (which for now includes returning dummy table data to test editing):
class SafariExtensionViewController: SFSafariExtensionViewController {
#IBOutlet weak var tableView: NSTableView!
static let shared: SafariExtensionViewController = {
let shared = SafariExtensionViewController()
shared.preferredContentSize = NSSize(width:445, height:421)
return shared
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func textDidEndEditing(_ notification: Notification) {
NSLog("I will NEVER appear in the console")
}
}
extension SafariExtensionViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return 5
}
}
extension SafariExtensionViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cellView = tableView.makeView(withIdentifier: tableColumn!.identifier, owner: self) as? NSTableCellView
cellView?.textField?.stringValue = "Blah"
return cellView
}
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
NSLog("I will appear in the console")
return true
}
func tableViewSelectionDidChange(_ notification: Notification) {
NSLog("I will NEVER appear in the console")
}
func controlTextDidEndEditing(_ obj: Notification) {
NSLog("I will NEVER appear in the console")
}
}
(Obviously I do not need both textDidEndEditing(_:) and controlTextDidEndEditing(_:) but I am just trying everything.)
I am guessing the problem is something to do with the table view not being registered for notifications within a SFSafariExtensionViewController? That object inherits from NSViewController, though, so I would have thought these methods should work automatically.
This is my first time using swift, and it is a long time since I wrote a Mac app. But the actual functionality of the extension works, now I just want to have the ability to customize the settings through the UI.
However there seems to be very little written about Safari app extension programming, Apple's documentation is sparse, and I have not even been able to find any code examples featuring a table view in a popover to learn from.
I am probably missing something very obvious, but I have run out of searches to try on here and the web in general, so any help will be appreciated.
UPDATE:
I think I have an answer, by explicitly linking the NSTextFields in the table to the File's Owner as a delegate, the tableViewSelectionDidChange(_:) and controlTextDidEndEditing(_:) methods are now working. There must have been something else wrong causing the former to not work that I accidentally broke and fix, but it makes some sense for the latter.
That is all I need for the functionality to work, however I am still confused why the textDidEndEditing(_:) is still not working when I am led believe it should.
And in Apple's documentation, textDidEndEditing(_ :) is a method of an NSTextField, which links to a page saying controlTextDidEndEditing(_ :) is deprecated
And I misunderstanding anything?
I think you are not setting up the outlet properly please confirm this. Also check you setting up reusable identifier? identifier. for me all delegate calling without no issue after that.

NSOutlineView leaks memory. What to do?

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?

Why is NSTableView not reloading?

I'm still very new to programming in Swift, (and never with Objective C). What I'm trying to do is add to the NSTableView when I've clicked on an item in the current tableview. The items seem to be adding when clicked, but the table does not seem to be refreshing with the new things in the array.
I've tried various things over the last few days, getting it to run reloadData on main thread and UI thread, etc but I'm feel like I'm just hitting the wall (it surely can't be this hard to do something so simple like I can in a couple minutes in Java)....
Have I missed something very obvious? (Code below)
class TableViewController: NSTableView, NSTableViewDataSource, NSTableViewDelegate {
var items: [String] = ["Eggs", "Milk"]
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
let result : TableCell = tableView.makeViewWithIdentifier(tableColumn!.identifier, owner: self) as! TableCell
result.itemField.stringValue = items[row]
return result
}
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
return items.count
}
func tableViewSelectionDidChange(notification: NSNotification) {
let index = notification.object!.selectedRow
if (index != -1) {
NSLog("#%d", index)
items.append("Blah")
NSLog("#%d", items.count)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.reloadData()
})
}
}
func relloadData() { //relload is not a typo
self.reloadData()
}
}
If you're binding your user interface to the items property of your view controller subclass, you need to mark it as it a Dynamic Variable:
dynamic var items: [String] = ["Eggs", "Milk"]
Place the dynamic keyword before the property declaration and I think it will solve your problem.
Note that Ken's comment also makes a good point in that this code probably should be written as an NSViewController subclass, instead of a subclass of NSTableView.
If you're using Storyboards, the custom view controller subclass would be a View Controller object for the view containing your table view. If you're using a xib file for the view, the view controller subclass would be the File's Owner. In either case, you would connect the table view's delegate and data source outlets to that object.

Swift - Why is argument item in NSOutlineView always nil?

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
}