App Crashes Due to Binding to Table Cell View - swift

So I've created an NSOutlineView to display the file & directory list in a hierarchical way. I'm building a BitTorrent client (stating so the class names make sense).
As you can see, this is pretty much how the outline view looks:
The problem is associated with the Name column. In the name column, for each row, I have a checkbox and a text field side by side. This will help you get a clearer idea:
Now, I use bindings to get the value for each textfield. However, since there are 2 views (checkbox and textfield) that needs to bound to the same NSTableCellView, I'm returning a struct, from the data source, containing 2 values: a string for the text field (which holds the file/directory name), and a boolean for enabling/disabling the checkbox.
To handle the outline view (especially its data), I've set its class to TorrentContent, which is defined as below:
import Cocoa
struct Name {
let value: String
let enabled: Bool
}
class TorrentContent: NSOutlineView, NSOutlineViewDelegate, NSOutlineViewDataSource {
var content: [TorrentContentItem]
required init?(coder: NSCoder) {
let srcDir = TorrentContentItem("src")
let mainJava = TorrentContentItem("main.java")
let mainCpp = TorrentContentItem("main.cpp")
srcDir.children.append(mainJava)
srcDir.children.append(mainCpp)
content = [srcDir]
super.init(coder: coder)
delegate = self
dataSource = self
}
func outlineView(_: NSOutlineView, isItemExpandable item: Any) -> Bool {
if let _item = item as? TorrentContentItem {
if _item.children.count > 0 {
return true
} else {
return false
}
} else {
return false
}
}
func outlineView(_: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if item == nil {
return content.count
} else {
if let _item = item as? TorrentContentItem {
return _item.children.count
}
}
return 0
}
func outlineView(_: NSOutlineView, child: Int, ofItem item: Any?) -> Any {
if item != nil {
if let _item = item as? TorrentContentItem {
return _item.children[child]
}
}
return content[child]
}
func outlineView(_: NSOutlineView, objectValueFor col: NSTableColumn?, byItem item: Any?) -> Any? {
if item != nil, col != nil {
if let _item = item as? TorrentContentItem {
switch col!.title {
case "Name":
return Name(value: _item.name, enabled: false)
default:
return nil
}
}
}
return nil
}
}
I've hard-coded the data so it'll be easier for you to understand what's going on.
Focusing only on the name column, here's the part of the above code which deals with that:
func outlineView(_: NSOutlineView, objectValueFor col: NSTableColumn?, byItem item: Any?) -> Any? {
if item != nil, col != nil {
if let _item = item as? TorrentContentItem {
switch col!.title {
case "Name":
return Name(value: _item.name, enabled: false)
default:
return nil
}
}
}
return nil
}
As you can see, it returns the Name struct, which contains values for both the views. I've hard-coded the enabled value to false just for testing purposes.
Now to bind that to the textfield's value property, I've done this:
My logic is that, since objectValue is an instance of the Name struct, objectValue.value should be the value of the Name struct's instance, which is a string.
I want to bind the enabled property of the checkbox in a similar way. However, none of the bindings work. They cause the app to crash. This is what XCode shows me after it crashes everytime I attempt to view the outline view during runtime:
Only got "(lldb)" in the console.
What am I doing wrong, and how do I achieve what I want? That is, setting the property values of multiple views from the data source class.

Cocoa Bindings uses Key Value Observing (KVO) and the observed object must be KVO compatible. See Using Key-Value Observing in Swift.
You can only use key-value observing with classes that inherit from NSObject.
Mark properties that you want to observe through key-value observing with both the #objc attribute and the dynamic modifier.
Solution A: Return a KVO compatble object from outlineView(_:objectValueFor:byItem:)
Solution B: Don't use Cocoa Bindings. Create a subclass of NSTableCellView and add a enabledCheckbox outlet. Set the values in outlineView(_:viewFor:item:).

Related

Why View-Based NSOutlineView with autosaveExpandedItems true ignores expanded upon reloadData?

I use a NSOutlineView that auto saves expanded state. If I manually reload data when dataSource updates, the func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? datasource method is not called anymore and every cell collapses. Any idea why this might happen?
Tried to reloadItem with nil send as param but still no good.
I use this for persisting expanded rows:
func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
return NSKeyedArchiver.archivedData(withRootObject: item)
}
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
guard let data = object as? Data,
let item = NSKeyedUnarchiver.unarchiveObject(with: data) as? Category else { return nil }
let foundItem = recursiveSearch(for: item, in: viewModel.dataSource.value)
return foundItem
}
And this to reloadData:
viewModel.dataSource.subscribe(onNext: { [weak self] _ in
self?.outlineView.reloadData()
}).disposed(by: disposeBag)
IMHO autosaving is sort of half-baked feature and it doesn't work as expected. In other words, it's implemented in a way that it restores the state when your application launches (just once) and then you're on your own.
Implement your own one utilizing outlineViewItemDidExpand(_:) & outlineViewItemDidCollapse(_:) (especially when we're reloading, ...).
Couple of tricks you can use if you do not want to implement custom autosaving. But I wouldn't rely on them.
First trick - tell the NSOutlineView to reload persistent state
NSOutlineView inherits from the NSTableView and the autosaveName property documentation says:
If you change the value of this property to a new name, the table reads in any saved information and sets the order and width of this table view’s columns to match. Setting the name to nil removes any previously stored state from the user defaults.
What is inaccurate here - setting it to nil doesn't remove previously stored expanded items state for NSOutlineView. We can use it to force the NSOutlineView to reload expanded items state:
class ViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource {
#IBOutlet var outlineView: NSOutlineView!
// It's for testing, to demonstrate the persistent state reloading
private var doNotLoad = true
override func viewDidAppear() {
super.viewDidAppear()
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.doNotLoad = false
let autosaveName = self.outlineView.autosaveName
self.outlineView.autosaveName = nil
self.outlineView.reloadData()
self.outlineView.autosaveName = autosaveName
}
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if (doNotLoad) {
return 0
}
return item == nil ? data.count : (item as! Node).children.count
}
}
If you'd like to comply with the documentation, do not use nil and set some fake name. But I would expect that once the bug is fixed, the persistent state will be removed if we change the autosaveName or if we set it set to nil.
Second trick - load & expand yourself
Imagine you have the following Node class:
class Node {
let id: Int
let children: [Node]
// ...
}
And your data source implements:
func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
(item as! Node).id
}
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
guard let id = object as? Int else { return nil }
return data.firstNode { $0.id == id }
}
The firstNode is not related to this question, but here's the implementation (because it's mentioned in the code):
extension Array where Self.Element == Node {
// Search for a node (recursively) until a matching element is found
func firstNode(where predicate: (Element) throws -> Bool) rethrows -> Element? {
for element in self {
if try predicate(element) {
return element
}
if let matched = try element.children.firstNode(where: predicate) {
return matched
}
}
return nil
}
}
Then you can reloadData & expand all the items by yourself:
outlineView.reloadData()
if outlineView.autosaveExpandedItems,
let autosaveName = outlineView.autosaveName,
let persistentObjects = UserDefaults.standard.array(forKey: "NSOutlineView Items \(autosaveName)"),
let itemIds = persistentObjects as? [Int] {
itemIds.forEach {
let item = outlineView.dataSource?.outlineView?(self.outlineView, itemForPersistentObject: $0)
self.outlineView.expandItem(item)
}
}

Next object instance IGListKit

Edit: I am new to the field, I did not get a response. Can anyone tell me if I am missing some information? or how I could improve it?
I want an instance from the next object to set a Label text in my current cell
cell.Label.text = talents(nextIndex).name //<- Something of this sort
Tried: passing array input to SectionController to use as
talents[index+1]
Error: File out of range
My Section Controller
class SectionController: ListSectionController {
var talents: Talent!
weak var delegate: SectionControllerDelegate?
}
extension SectionController {
override func sizeForItem(at index: Int) -> CGSize {
guard
let context = collectionContext
else {
return .zero
}
let width = context.containerSize.width
let height = context.containerSize.height
return CGSize(width: width, height: height)
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
guard let cell = collectionContext?.dequeueReusableCellFromStoryboard(withIdentifier: "HomeCell",
for: self,
at: index) as? HomeCell else {
fatalError()
}
cell.nameLabel.text = talents.name
cell.descView.text = talents.largeDesc
cell.shortDesc.text = talents.smallDesc
// let nextTalent = talents[index+1]
// cell.nextIntroFunc(nextlabels: nextTalent)
return cell
}
override func didUpdate(to object: Any) {
talents = object as? Talent
}
}
My ListAdapterDataSource
extension Home: ListAdapterDataSource {
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
print(talents)
return talents
}
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any)
-> ListSectionController {
return SectionController()
}
func emptyView(for listAdapter: ListAdapter) -> UIView? {
print("emptyView")
return nil
}
}
talents in the section controller isn't an array. It's just a single Talent, so calling [index + 1] on it is going to throw an error. (You might want to rename the property of the model in the section controller to talent so it's less confusing.)
Best practice for ListSectionController is to keep a 1:1 relationship between your object model and the section controller. If you want to have reference to the next object, you could create a cell/view model layer based on talent that includes reference to the next talent as well.
For example:
class TalentSectionModel: ListDiffable {
let talent: Talent
let nextTalent: Talent?
init(_ talent: Talent, nextTalent: Talent?) {
self.talent = talent
self.nextTalent = nextTalent
}
// whatever properties you want to expose
var name: String {
return talent.name
}
// example of exposing info about next talent
var nextTalentName: String? {
return nextTalent?.name
}
// ListDiffable protocol
// make sure to implement these two so diffing works
func diffIdentifier() -> NSObjectProtocol {}
func isEqual(toDiffableObject object: ListDiffable?) -> Bool {}
}
You could also just only pass in the necessary properties from the next talent without passing in the entire nextTalent if you don't want the section model to store the entire object.
Now with the TalentSectionModel you would map over your talents array in Home and return an array of view models in objects() under ListAdapterDataSource. Then, in your section controller you'll have the next talent's information available to you through the view model.
Resources:
ListKit Modeling and Binding (talks about the ListBindingSectionController, but also helpful to read for general understanding of model and section controller relationship),
ListKit best practices

Building an NSOutline view with Check marks

I am looking to add checkboxes to NSOutlineview using the correct Apple recommended method - however its not clear from the documentation.
How do I add the behavour to allow users whereby if I click a parent checkbox, then it will select the children, and if I unclick it - it will deselect the children of that item?
edit: I have simplified my question and added image to make it clearer ( hopefully)
My Approach:
I have been using the wonderful answer by Code Different to build an Outline view in my mac app.
https://stackoverflow.com/a/45384599/559760
- I chose to populate the NSoutLine view using a manual process instead of using CocoaBindings.
I added in a stack view including a check box which seems to be the right approach:
My solution involves creating an array to hold the selected items in the viewcontroller and then creating functions for adding and removing
var selectedItems: [Int]?
#objc func cellWasClicked(sender: NSButton) {
let newCheckBoxState = sender.state
let tag = sender.tag
switch newCheckBoxState {
case NSControl.StateValue.on:
print("adding- \(sender.tag)")
case NSControl.StateValue.off:
print("removing- \(sender.tag)")
default:
print("unhandled button state \(newCheckBoxState)")
}
I identify the checkbutton by the tag that was assigned to the checkbox
In the interest of future Googlers I will repeat things I've written in my other answer. The difference here is this has the extra requirement that a column is editable and I have refined the technique.
The key to NSOutlineView is that you must give an identifier to each row, be it a string, a number or an object that uniquely identifies the row. NSOutlineView calls this the item. Based on this item, you will query your data model to populate the outline.
In this answer we will setup an outline view with 2 columns: an editable Is Selected column and a non-editable Title column.
Interface Builder setup
Select the first column and set its identifier to isSelected
Select the second column and set its identifier to title
Select the cell in the first column and change its identifier to isSelectedCell
Select the cell in the second column and change its identifier to titleCell
Consistency is important here. The cell's identifier should be equal to its column's identifier + Cell.
The cell with a checkbox
The default NSTableCellView contains a non-editable text field. We want a check box so we have to design our own cell.
CheckboxCellView.swift
import Cocoa
/// A set of methods that `CheckboxCelView` use to communicate changes to another object
protocol CheckboxCellViewDelegate {
func checkboxCellView(_ cell: CheckboxCellView, didChangeState state: NSControl.StateValue)
}
class CheckboxCellView: NSTableCellView {
/// The checkbox button
#IBOutlet weak var checkboxButton: NSButton!
/// The item that represent the row in the outline view
/// We may potentially use this cell for multiple outline views so let's make it generic
var item: Any?
/// The delegate of the cell
var delegate: CheckboxCellViewDelegate?
override func awakeFromNib() {
checkboxButton.target = self
checkboxButton.action = #selector(self.didChangeState(_:))
}
/// Notify the delegate that the checkbox's state has changed
#objc private func didChangeState(_ sender: NSObject) {
delegate?.checkboxCellView(self, didChangeState: checkboxButton.state)
}
}
Connecting the outlet
Delete the default text field in the isSelected column
Drag in a checkbox from Object Library
Select the NSTableCellView and change its class to CheckboxCellView
Turn on the Assistant Editor and connect the outlet
The View Controller
And finally the code for the view controller:
import Cocoa
/// A class that represents a row in the outline view. Add as many properties as needed
/// for the columns in your outline view.
class OutlineViewRow {
var title: String
var isSelected: Bool
var children: [OutlineViewRow]
init(title: String, isSelected: Bool, children: [OutlineViewRow] = []) {
self.title = title
self.isSelected = isSelected
self.children = children
}
func setIsSelected(_ isSelected: Bool, recursive: Bool) {
self.isSelected = isSelected
if recursive {
self.children.forEach { $0.setIsSelected(isSelected, recursive: true) }
}
}
}
/// A enum that represents the list of columns in the outline view. Enum is preferred over
/// string literals as they are checked at compile-time. Repeating the same strings over
/// and over again are error-prone. However, you need to make the Column Identifier in
/// Interface Builder with the raw value used here.
enum OutlineViewColumn: String {
case isSelected = "isSelected"
case title = "title"
init?(_ tableColumn: NSTableColumn) {
self.init(rawValue: tableColumn.identifier.rawValue)
}
var cellIdentifier: NSUserInterfaceItemIdentifier {
return NSUserInterfaceItemIdentifier(self.rawValue + "Cell")
}
}
class ViewController: NSViewController {
#IBOutlet weak var outlineView: NSOutlineView!
/// The rows of the outline view
let rows: [OutlineViewRow] = {
var child1 = OutlineViewRow(title: "p1-child1", isSelected: true)
var child2 = OutlineViewRow(title: "p1-child2", isSelected: true)
var child3 = OutlineViewRow(title: "p1-child3", isSelected: true)
let parent1 = OutlineViewRow(title: "parent1", isSelected: true, children: [child1, child2, child3])
child1 = OutlineViewRow(title: "p2-child1", isSelected: true)
child2 = OutlineViewRow(title: "p2-child2", isSelected: true)
child3 = OutlineViewRow(title: "p2-child3", isSelected: true)
let parent2 = OutlineViewRow(title: "parent2", isSelected: true, children: [child1, child2, child3])
child1 = OutlineViewRow(title: "p3-child1", isSelected: true)
child2 = OutlineViewRow(title: "p3-child2", isSelected: true)
child3 = OutlineViewRow(title: "p3-child3", isSelected: true)
let parent3 = OutlineViewRow(title: "parent3", isSelected: true, children: [child1, child2, child3])
child3 = OutlineViewRow(title: "p4-child3", isSelected: true)
child2 = OutlineViewRow(title: "p4-child2", isSelected: true, children: [child3])
child1 = OutlineViewRow(title: "p4-child1", isSelected: true, children: [child2])
let parent4 = OutlineViewRow(title: "parent4", isSelected: true, children: [child1])
return [parent1, parent2, parent3, parent4]
}()
override func viewDidLoad() {
super.viewDidLoad()
outlineView.dataSource = self
outlineView.delegate = self
}
}
extension ViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
/// Returns how many children a row has. `item == nil` means the root row (not visible)
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
switch item {
case nil: return rows.count
case let row as OutlineViewRow: return row.children.count
default: return 0
}
}
/// Returns the object that represents the row. `NSOutlineView` calls this the `item`
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
switch item {
case nil: return rows[index]
case let row as OutlineViewRow: return row.children[index]
default: return NSNull()
}
}
/// Returns whether the row can be expanded
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
switch item {
case nil: return !rows.isEmpty
case let row as OutlineViewRow: return !row.children.isEmpty
default: return false
}
}
/// Returns the view that display the content for each cell of the outline view
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
guard let item = item as? OutlineViewRow, let column = OutlineViewColumn(tableColumn!) else { return nil }
switch column {
case .isSelected:
let cell = outlineView.makeView(withIdentifier: column.cellIdentifier, owner: self) as! CheckboxCellView
cell.checkboxButton.state = item.isSelected ? .on : .off
cell.delegate = self
cell.item = item
return cell
case .title:
let cell = outlineView.makeView(withIdentifier: column.cellIdentifier, owner: self) as! NSTableCellView
cell.textField?.stringValue = item.title
return cell
}
}
}
extension ViewController: CheckboxCellViewDelegate {
/// A delegate function where we can act on update from the checkbox in the "Is Selected" column
func checkboxCellView(_ cell: CheckboxCellView, didChangeState state: NSControl.StateValue) {
guard let item = cell.item as? OutlineViewRow else { return }
// The row and its children are selected if state == .on
item.setIsSelected(state == .on, recursive: true)
// This is more efficient than calling reload on every child since collapsed children are
// not reloaded. They will be reloaded when they become visible
outlineView.reloadItem(item, reloadChildren: true)
}
}
Result

Reload a NSWindow Xcode Swift2

I'm working on an NSOutlineView that uses NSView subclasses to generate custom cells in the outline. This I've gotten to work, BUT after the Outline sucks in the data from the model class and displays it correctly, the Outline is released(?) from memory / goes to nil and I haven't figured out a way to get it back.
Here is the MainViewController class
class MainWindowController: NSWindowController, ShareInfoDelegate, NSOutlineViewDelegate, NSOutlineViewDataSource {
override var windowNibName: String {
return "MainWindowController"
}
#IBOutlet var daOutline: NSOutlineView!
// The NSoutline I'm trying to get back to
Some stuff related to the test data (Omitted)
leading us to the NSOutlineViewDataSource stuff
//MARK: - NSOutlineViewDataSource
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
if let item: AnyObject = item {
switch item {
case let work as Work:
return work.movements[index]
case let movement as Movement:
return movement.tracks[index]
default:
let track = item as! Track
return track.credits[index]
}
} else {
if allWorks.count > 0 {
return allWorks[index]
}
}
let q = "patience"
return q
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
switch item {
case let work as Work:
return (work.movements.count > 0) ? true : false
case let movement as Movement:
return (movement.tracks.count > 0) ? true : false
case let track as Track:
return (track.credits.count > 0) ? true: false
default:
return false
}
}
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
if let item: AnyObject = item {
switch item {
case let work as Work:
return work.movements.count
case let movement as Movement:
return movement.tracks.count
case let track as Track:
return track.credits.count
default:
return 0
}
} else {
return allWorks.count
}
}
func outlineView(daOutline: NSOutlineView, viewForTableColumn theColumn: NSTableColumn?, item: AnyObject) -> NSView? {
switch item {
case let worked as Work:
let cell = daOutline.makeViewWithIdentifier("newTry", owner:self) as! newTry
cell.fourthLabel.stringValue = worked.composer
cell.fourthCell.stringValue = worked.title
return cell
case let moved as Movement:
let cell2 = daOutline.makeViewWithIdentifier("SecondTry", owner:self) as! SecondTry
cell2.roman.stringValue = moved.name!
cell2.details.stringValue = moved.sections!
cell2.track.stringValue = "0"
return cell2
default:
print("probably not")
}
print("not again")
return nil
}
func outlineView(daOutline: NSOutlineView, heightOfRowByItem item: AnyObject) -> CGFloat {
switch item {
case let worked as Work:
return 40
default:
return 24
}
}
And the stuff in WindowDidLoad
override func windowDidLoad() {
super.windowDidLoad()
let nib = NSNib(nibNamed: "newTry", bundle: NSBundle.mainBundle())
daOutline.registerNib(nib!, forIdentifier: "newTry")
let nib2 = NSNib(nibNamed: "SecondTry", bundle: NSBundle.mainBundle())
daOutline.registerNib(nib2!, forIdentifier: "SecondTry")
//give Sender it's Receiver
mailItOut.delegate = receiver
allWorks.append(work1)
allWorks.append(work2)
work1.movements.append(move1)
work1.movements.append(move2)
work1.movements.append(move3)
work1.movements.append(move4)
work2.movements.append(move5)
work2.movements.append(move6)
work2.movements.append(move7)
daOutline.reloadData()
daOutline?.expandItem(work1, expandChildren: false)
daOutline?.expandItem(work2, expandChildren: false)
}
}
And Finally what the newTry NSView class looks like
class newTry: NSView {
var delegate: ShareInfoDelegate?
#IBOutlet weak var fourthCell: NSTextField!
#IBOutlet weak var fourthLabel: NSTextField!
#IBAction func cellAdd(sender: NSTextField) {
var catchIt: String = String()
catchIt = sender.stringValue
if catchIt != "" {
tryAgain = catchIt
whichField = "title"
//Trigger the sender to send message to it's Receiver
mailItOut.sendMessage()
}
}
The cellAdd Action is used to try and get user input from the text cells back into the model. To do this I (AFAIK) need to access the NSOutline (daOutline) and get which row I'm at and put the data from the sender into the appropriate part of the Model class. Which is something that I've managed to get to work in a standard (1 cell / 1 data value) outline. But in this prototype, as far as I can tell, the MainWindowController has released all of its contents and daOutline is nil (bad).
How do I get XCode to bring / reload the completed outline (or never release it) and get daOutline to a non nil state?
For those who come after there appeared to be two problems that led to the NSOutline outlet becoming nil. The first one was that in implementing the delegate protocol "shareInfoDelegate" I was creating a new instance of the MainWindowController, not the one with the data in it. This new instance did NOT have the IBOutlets connected (or much of anything useful about it).
Once I scrapped the Delegate and moved to using NSNotification to update information about the NSView textFields my NSOutline came "back".
The second, more minor, problem was that in the NSView nib file I placed and NSBox to mimic the behavior of a group row (e.g. a gray background). As a side effect the NSBox was inhibiting the normal row select behavior of the outline. Which made it very hard to determine which row was selected. When I deleted the NSBox, row selection became much more easy to determine.
in particular this Question and the answer by Chuck were helpful in sniffing this out.
Why is my NSOutlineView datasource nil?
Thanks Indeed(!)

nil is not convertible to hashable.

I have a custom control that is using a datasource to fetch items (as an NSTableView would do). The datasource can return Any-type, as long as it's hashable. The items are used as a key in a private dictionary.
The control (custom view) is added to the UI in interface builder.
I run into problems when I am querying the datasource with a nil parameter because nil is not convertible to hashable.
What is the proper way to do this?
protocol DataSourceProtocol
{
func numberOfChildrenOfItem<Item: Hashable>(item: Item?) -> Int
func child<Item: Hashable>(index: Int, ofItem item: Item?) -> Item
}
class MyControl : NSControl
{
var dataSource : DataSourceProtocol!
func reloadData()
{
//using string as an example of a hashable
let countA = dataSource.numberOfChildrenOfItem("item") // ok
let countB = dataSource.numberOfChildrenOfItem(nil) // not ok
let childA = dataSource.child(0, ofItem: "item") //ok
let childB = dataSource.child(0, ofItem: nil) //not ok
self.reloadChildren(childA)
self.reloadChildren(childB)
}
func reloadChildren<Item: Hashable>(item: Item)
{}
}
Use NSNull() to get a null object, which you can then compare to another NSNull() to see if its empty or not.