ASObjC Source List with Static Values

I want to create a list of options on the left of an interface that controls what is on the right side. Xcode has a widget for a “Source List” which fits the bill perfectly. When you drag it into the interface, it defaults with one heading and one view cell and allows you to add more cells to it. This seems to imply that it is possible to assemble the view and then wire it up so a selection triggers a subroutine. However, when running the app, the control appears but is blank.

I see older posts talking about the complexity of setting up a source list but am curious if there is any new information given what appears to be possible when you drag this thing on the layout.

Any advice on the best/easiest way to setup a neat looking list of buttony/tabby kinds of options for a sidebar?

A source list is just an NSOutlineView with a pre-designed table cell view, so you deal with it like any outline view. For bindings that means using a tree controller, and that’s a reasonably complex thing to do. The one time I tried it, I ended up using a datasource instead – but that was still fairly challenging (and done in Objective-C).

After fidgeting around, I found this and the second answer gets me maddeningly close to what I want. I have a pretty easy setup and table rendering with non-selectable headings and selectable entries under them. I can customize is easy with a record. However… I am just clueless enough to not be able to figure out how to adjust the styling of the table rows by type so it has a funny default look… Any advice to use the example 2 on this link but tweak the code to apply font/style/size to individual table rows?

https://stackoverflow.com/questions/57384941/how-to-use-a-sidebar-with-applescript

Hey, it’s Ted. Can you give some idea of the kind of styling you want? It’s easier to work with something specific.

Sure. So, this is the code that I pulled from the link I posted previously. I wired it into an interface following the instructions on that page and it creates a table with my values formatted either as a header or regular.

script AppDelegate
    property parent : class "NSObject"

    
    -- IBOutlets
    property theWindow : missing value
    property arrayController : missing value
    property detailView : missing value
    property refTable : missing value
    
    
    on applicationWillFinishLaunching_(aNotification)
        -- Insert code here to initialize your application before any files are opened
        (* set up list of headers and lines for the side bar *)
        set sidebarList to {{title:"Header 1", isHeader:true}, {title:"Line 1", isHeader:false}, {title:"Header 2", isHeader:true}, {title:"Line 2", isHeader:false}, {title:"Line 3", isHeader:false}, {title:"Header 3", isHeader:true}, {title:"Line 4", isHeader:false}}
        arrayController's addObjects:sidebarList
        
    end applicationWillFinishLaunching_
    
    on applicationShouldTerminate_(sender)
        -- Insert code here to do any housekeeping before your application quits
        return current application's NSTerminateNow
    end applicationShouldTerminate_
    
    (* table view delegate methods *)
 
    on tableView:tableView isGroupRow:row
    
        -- header rows are group rows
        set rowData to arrayController's arrangedObjects's objectAtIndex:row
        return rowData's isHeader
    end

   
    on tableView:tableView shouldSelectRow:row
        -- don't want to select header rows
        set rowData to arrayController's arrangedObjects's objectAtIndex:row
        return not (rowData's isHeader)
    end
   
    on tableView:tableView viewForTableColumn:column row:row
        -- header rows get a special look
        set aView to tableView's makeViewWithIdentifier:"tableItem" owner:me
        return aView
    end
    
    on tableViewSelectionDidChange:aNotification
        (*
         This is method gets notified right after a selection is made. This is one of
         the places where you can change the detail view to show a the correct view for
         selected sidebar item. For demonstration purposes I'm just swapping out
         TextField views with the name of the sidebar item. Not to sophisticated,
         but it get the point across
         *)
        set tableView to aNotification's object
        set selectedRowIdx to (tableView's selectedRow) as integer
        log "Picked Row " & selectedRowIdx
        set rowData to arrayController's arrangedObjects's objectAtIndex:selectedRowIdx
        set newLabel to current application's NSTextField's labelWithString:(rowData's title )
        set newLabel's translatesAutoresizingMaskIntoConstraints to false
        set detailSubviews to (detailView's subviews) as list
        if detailSubviews is not {} then
            set oldLabel to item 1 of detailSubviews
            detailView's replaceSubview:oldLabel |with|:newLabel
            else
            detailView's addSubview:newLabel
        end
        
        set constraintX to newLabel's centerXAnchor's constraintEqualToAnchor:(detailView's centerXAnchor)
        set constraintY to newLabel's centerYAnchor's constraintEqualToAnchor:(detailView's centerYAnchor)
        constraintX's setActive:true
        constraintY's setActive:true
    end
    
end script

The regular entries are largely ok, looking like the normal font/size/alignment for table rows. But the heads are the same font/size as regular (which I might want to change) but, worse, they have extra height, are aligned to the top of that and have a gray background and are bordered by horizontal lines. Not only do I not now how to change that formatting, but I’m not completely clear where to put the code in the script. I am used to putting table data in place via bindings instead of addObjects and still have a major mental block when it comes to ASObjC and translating an Apple ObjC documentation.

I also see a potential hurdle in programmatically selecting a specific row… and, by default, this code is selecting every row when it populates the table. So that is another issue I could use some pointers on.

I hope that all makes sense.

Any advice you can offer would be most appreciated.

You don’t need to post the code for me; I’m the one who wrote it over at Stackoverflow, so I have a copy of it.

Simplest things first. To keep the table from selecting every row on insert, un-click ‘Select inserted objects’ in the Attributes Inspector of the Array Controller object. And don’t worry about selecting a row programmatically. whenever the selection changes, regardless of how or why it changes (with a few minor exceptions), the tableview will call its delegate and function properly. On a certain level you have to trust that things will work; avoid worrying until you see an actual problem.

With respect to visual aesthetics… First, in the table view’s Attributes Inspector, you might want to change the highlight option to SourceList. It’s a smoother look, but it’s more difficult to adjust programmatically. (Sorry, I’m not sure how to post images here, or I’d show you)

There are two kinds of view you see in a table view: Table Cell Views (NSTableCellView) and Table Row Views (NSTableRowView). Cell views are the normal views used to display table cells in columns; row views are views used to display groups rows (like the headers used in this sidebar). You have to at least one cell view defined in the nib, but the table view will define a default row view for you (which is what you’re seeing currently). You can, of course, create as many cell and row views as you like and call them programmatically (by identifier) for different contexts.

These delegate methods allow you to assign (or even programmatically create) row and cell views for specific data items:

on tableView:tableView viewForTableColumn:tableColumn row:row
    --
end

on tableView:tableView rowViewForRow:row
    --
end

… while these delegate methods allow you to catch a cell or row before display and modify some of its visual aspects:

on tableView:tableView willDisplayCell:cell forTableColumn:tableColumn row:row
    --
end

on tableView:tableView didAddRowView:rowView forRow:row
    --
end

You might also find this method useful, which lets you set row heights individually:

on tableView:tableView heightOfRow:row
    --
end

As far as specific implementations go, I’ll throw out two options:

Option 1

This is easiest, and only involves going to the Table View’s Attributes inspector in the xib and changing the Highlight option to Source List. Source lists have a smoother look which you might like out of the box, but it is hard to change header items (I think it’s possible, but I’d have to play with it a bit). For instance, if you add this method:

on tableView:tableView didAddRowView:rowView forRow:row
	set rowCell to rowView's subviews's objectAtIndex:0
	set rowTextField to rowCell's textField
	rowTextField's setTextColor:(current application's NSColor's greenColor)
end

it will change all of the regular lines to green, but not the headers.

Option 2

This involves forgetting about using grouping rows (which aren’t really necessary anyway since this is a simple, flat source list). Keep the Table View’s Highlight option at Regular, delete the entire ‘tableView:isGroupRow:’ method, and then pull a second Table Cell View out of the object library and add it to the table column. Give that one the identifier (say) ‘headerItem’, and rewrite the ‘tableView:viewForTableColumn:row:’ method like so:

on tableView:tableView viewForTableColumn:column row:row
	-- header rows get a special look
	set rowData to arrayController's arrangedObjects's objectAtIndex:row

	if rowData's isHeader as boolean is false then
		set aView to tableView's makeViewWithIdentifier:"tableItem" owner:me
	else
		set aView to tableView's makeViewWithIdentifier:"headerItem" owner:me
	end
	return aView
end

Then style the two Table Cell Views in IB to get the look you want for the headers and rows. This method will now call a different view for headers and regular items.

Ted,

Thank you much for the detailed response and for that original post which was hugely useful.

Just changing the Table’s highlight to Source List made a huge difference and a leap towards what I want. Didn’t know that was an option.

Formatting

So now I am less worried about font/size/color type applications and more wondering if I can get those headers to hug the bottom of their row instead of the top… or possibly make the row height the same. I’m not sure if I missed a step, but when I put the tableView:tableView heightOfRow:row in there with only a logging comment to see when it is hit I got a crash.

Another tweak that would be nice is to be able to affect the indent of headers and body rows. Right now they are very close to the left side of the table.

I’ll keep exploring that and see if I can’t figure it out but might need guidance on what can be done to make it look one notch nicer.

Selection

The initial selection issue is resolved too with that setting. However, now nothing is selected at the start and I do need to be able to programmatically make a selection… at least at the beginning. I tried selectRow and got weird results.

I got things to indent by changing the Table View Cell’s X setting in the View area of the Size Inspector… FYI

I also got it to programmatically select with this:


 set indexSet to current application's NSMutableIndexSet's alloc()'s init()
indexSet's addIndex_(1)
tell refTable to selectRowIndexes_byExtendingSelection_(indexSet, false)

Not sure if that’s the best way…

Now, if I could just get the headers to align bottom.

Is there a similar handler for header rows like the one you posted:

tableView:tableView didAddRowView:rowView forRow:row

To get the headers to align bottom, try combining Options 1 and 2. You’ll use the Source List setting, and keep the ‘tableView:isGroupRow’ method to make the headers group rows as in Option 1 (that gives you the Source List look). But then you’ll add a second Table Cell View to the table (you can just duplicate the first one), give it the identifier ‘headerItem’, and use the ‘tableView:viewForTableColumn:row:’ method outlined in Option 2.

Now go to that second Table Cell View in the xib, click on its Table View Cell, go to the size inspector, and look at the autoresizing box near the bottom. You’ll see that it’s attached at the left, right, and top. Click on the top attachment to turn that off, then click on the bottom to turn on the attachment there. That’s good to go.

If I were going to try to indent the table cell, I would probably either change the tableView’s intercellSpacing property or set actual layout constraints on the TextFiledView in the xib. But whatever works, works…

Best way to set the selection is to go through the array controller. You can use this command to (for instance) set the selection to Line 1;

arrayController's setSelectionIndex:1

I’m not sure what will happen if you try to select one of the headers.

The equivalent of ‘didAddRow:’ is ‘willDisplayCell:’, as I noted above.

THAT WORKS!!! THANK YOU!!!

I have a beautiful looking source list, with non-selectable groups, a selection of an item below it triggers a subroutine… Will need to clean up my code a little and then try transplanting this test into my app.

FYI one thing that is weird is that the first header is stuck in place and when you scroll the item below overlaps it… This won’t affect me because the list is static and will always fit without scrolling, but if there is a fix for that it might be nice.

Also a little weird… everything works fine but I notice it is now generating two errors right away:

Failed to set (title) user defined inspected property on (NSArrayController): [<NSArrayController 0x600003d00c30> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key title.
Failed to set (isHeader) user defined inspected property on (NSArrayController): [<NSArrayController 0x600003d00c30> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key isHeader.

Per the first point: xib->Table View->Attributes inspector: is the ‘Float group rows’ checkbox clicked off? sounds like it’s on…

For the error, I’m not seeing that on my end. It’s odd, in fact, that the code is even trying to set the title anywhere. Can you narrow down where that error is occurring? Usually Xcode gives a line number or a method name.

First point was the problem. Thanks.

For the errors, I don’t see a line number and I can confirm that it is happening before anything in applicationWillFinishLaunching_ runs. But I found that I had those two keys entered in two places in the table settings…user defined runtime areas… and when I remove them the error goes away and so fare everything still works. So that might now be a non-issue.

I wonder if it is possible to add a second column to the regular title entries?

That’s easy enough: go to the table view’s Attributes inspector and change the columns from 1 to 2. Then you’ll have to set up and bind a new table cell view for the new column.

Thanks for all the help @Ted Wrigley, glad my question and your answer are contributing to others,
i’m still trying to figure out how to use a .xib file for each selected sidebar item.
Could you help me with this?

Hah! Sorry, I thought that Mark was you: he was following the exact same line of thought, and even linked back to the StackOverflow discussion we were having. Too funny… :smiley:

I’ll get back to you on this tomorrow afternoon. I know how to implement this in theory, but I’ve never actually done it, so some puddling around is in order.

Mark must be trying to do the same as me, or we had the same idea … really really funny :lol: ok wait for your reply Ted, thanks for the help always.