Group Group Group Group Group Group Group Group Group

raywenderlich.com Forums

NSOutlineView on macOS Tutorial

Discover how to display and interact with hierarchical data on macOS with this NSOutlineView on macOS tutorial.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/1201-nsoutlineview-on-macos-tutorial

@pierredrks

Setting the width in the size inspector for the feed and date cells doesn’t seem to do anything. IB refuses to alter the actual size of the cells. Should I be pinning their widths with an autolayout constraint?

Hi @rcritz, the only thing that should have constraints are the Table Cell Views below the date and feed columns. The have a constraint for leading and vertical center. I tested it with the final project and changing the size of a column works for me. Can you check minimum and maximum values for the columns?

You’ll find them in the size inspector. If you try to set the size below or over the min/max value IB will refuse this change.

@pierredrks Still not working correctly. Trying to follow the steps in the tutorial of setting the date column’s width to 102 and the ‘feed’ column’s width to 320. The instruction in the tutorial implies to select the TableViewCell itself in the document outline, which one must do in order to set its identifier. However, you must then move up the containment tree to the column itself to change the width. That part works, but it doesn’t correctly resize the Outline View to make the whole thing visible.

In other words, I’m still not able to reproduce the results of the tutorial and I’m not sure what I’m doing wrong.

Would it be possible to adapt this to a multi level tree? As in:

Feed
Item
Item
Feed
Item
Item
Feed
Item

Sorry, indents not working well here. This:

Feed
—>Item
—>Item
—>Feed
------>Item
------>Item
--------->Feed
------------>Item
------------>Item
etc

As in, say, a directory tree.

@kimaldis sure, in this case FeedItem needs also children and you must change some of OutlineView methods, have a look here (48.3 KB). This is just a quick solution and the important stuff is inside FeedItem.swift and in the OutlineView extension of ViewController.swift.

@rcritz
I don’t get the problem, what do you expect and what is the result you have?

Brilliant, thanks.

One last question; changing the directory arrow icon and adding leading icons to filenames? Possible?

Sure, you can create your own subclass of a cell. But before you should have a look at our NSTableView tutorial. It shows how you can do the second part of your question.

Brilliant. Thanks. And thanks once again for the very clear tutorial.

I believe there’s a typo/bug in the code for deleting a feed. To reproduce the bug:

  1. Launch the app
  2. Expand the raywenderlich.com feed item
  3. Select the swift.org feed item
  4. Hit the delete key

When I do that I get the error NSOutlineView error removing child indexes <_NSCachedIndexSet: 0x6000000256a0>[number of indexes: 1 (in 1 ranges), indexes: (5)] in parent 0x0 (which has 2 children).

I believe the line

// 7
outlineView.removeItems(at: IndexSet(integer: selectedRow), inParent: nil, withAnimation: .slideLeft)

should be

// 7
outlineView.removeItems(at: IndexSet(integer: index), inParent: nil, withAnimation: .slideLeft)

So now I’m having fun, but I’m finding your doubleClickedItem action is behaving differently to mine. My action is called on a single click, if I double click I get to edit the item name, which then reverts to the old name on CR.

The single click works for me, what I’d like to do is trap the double click also. Any thoughts?

This is a great tutorial, and it works great, for the example given where the data is all loaded at once, before the view really appears. The trouble I’m having is figuring out how to have this work where the data source array isn’t populated until later.

When this runs, the func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int method is called as soon as the view loads, but the data source has no data in it yet. Adding data later to the data source does not cause that method to ever be called, or for data to ever be updated in the OutlineView.

Any ideas on how to accomplish that task?

NSOutlineView is a subclass of NSTableView so you can use NSTableViews reloadData() method after you added all data to your data source. If you want to add rows to a populated outline view you can use insertItems(at:, inParent:, withAnimation:).

Hi,

I am having trouble displaying a checkbox in a separate column. I’ve looked all over google and can’t really find a straightforward answer. Is there some function in outlineView that will display the checkbox button? I’ve been stuck on this for hours.

Hi pierredrks, Great tutorial for OS X. I have two questions:

  1. How to remove the gridline in outline view? In my test, it shows gridline in outline view for somehow,
  2. How to make the view round cornered?
    I googled a lot but found nothing useful, so can you give some suggestion based on this tutorial?
    thanks.

I just found the reason for the first problem: if let following method returns false, then gridlines disappear, otherwise gridlines appear. But I don’t really understand what this method for.
func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool {
return false
}

I converted the code for this tutorial to Objective C in case anyone’s interested https://github.com/demianturner/OutlineView2017-Mac

Great tutorial, thanks a lot. Just a small question: Do the model classes need to be subclasses of NSObject (due to some internal restriction from the part of NSOutlineView, perhaps?) or will native Swift classes work just as well?

1 Like

Oof, I realize this is about a year old now, but just for clarity, in the reader 2 example there with the new subitems:

If I want to load similar stuff from, for example, a plist, am I correct in thinking there’s just an extra step in the feedlist func in the Feed class?

What I tried was:

		        if let feedList = NSArray(contentsOfFile: fileName) as? [NSDictionary] {
        			for feedItems in feedList {
        				let feed = Feed(name: feedItems.object(forKey: "name") as! String)
        				let items = feedItems.object(forKey: "items") as! [NSDictionary]
        				for dict in items {
        					let item = FeedItem(dictionary: dict)
        					let subItems = dict.object(forKey: "subitem") as! [NSDictionary]
        					for each in subItems {
        						let otherItem = SubItem(dictionary: each)
        						item.subItems.append(otherItem)
        					}
        					feed.children.append(item)
        				}
        				feeds.append(feed)
        			}
        		}
        		return feeds
        	}

Now, in my test case I’ve got a new variable in FeedItem:

let subItems = [SubItem]

And just to test it out my SubItem class just has a name and initializes like a FeedItem by taking the value for a “name” key from the plist.

In the plist i’ve added a new dictionary inside item 0 of the items array named subitem. It tells me I appear to be trying to cast an NSDictionary as an NSArray but I can’t tell where that’s happening. It seemed like maybe it was just a case of nesting these things but apparently that is not the case.

Apparently I can’t upload my commented project as an attachment as I’m new to the forum. Basically, what’s the process for loading other sub-items from the plist rather than making an array manually?

I realize that Plist is only good for relatively small amounts of data.