Tuesday, August 20, 2019

#1 2019-08-10 07:48:23 am

markconwaymunro
Member
Registered: 2017-11-28
Posts: 18

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?

Offline

 

#2 2019-08-10 08:32:41 am

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5825

Re: ASObjC Source List with Static Values

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).


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Offline

 

#3 2019-08-13 12:56:22 pm

markconwaymunro
Member
Registered: 2017-11-28
Posts: 18

Re: ASObjC Source List with Static Values

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/573 … pplescript

Offline

 

#4 2019-08-14 10:15:56 pm

TedW
Member
Registered: 2005-11-02
Posts: 20

Re: ASObjC Source List with Static Values

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

Offline

 

#5 2019-08-15 06:14:18 am

markconwaymunro
Member
Registered: 2017-11-28
Posts: 18

Re: ASObjC Source List with Static Values

TedW wrote:

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.

Applescript:

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.

Offline

 

#6 2019-08-15 12:04:53 pm

TedW
Member
Registered: 2005-11-02
Posts: 20

Re: ASObjC Source List with Static Values

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:

Applescript:

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:

Applescript:

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:

Applescript:

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:

Applescript:

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:

Applescript:

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.

Offline

 

#7 2019-08-15 12:38:29 pm

markconwaymunro
Member
Registered: 2017-11-28
Posts: 18

Re: ASObjC Source List with Static Values

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.

Offline

 

#8 2019-08-15 12:54:55 pm

markconwaymunro
Member
Registered: 2017-11-28
Posts: 18

Re: ASObjC Source List with Static Values

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

Offline

 

#9 2019-08-15 01:06:27 pm

markconwaymunro
Member
Registered: 2017-11-28
Posts: 18

Re: ASObjC Source List with Static Values

I also got it to programmatically select with this:

Applescript:


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:

Applescript:

tableView:tableView didAddRowView:rowView forRow:row

Offline

 

#10 2019-08-15 02:29:53 pm

TedW
Member
Registered: 2005-11-02
Posts: 20

Re: ASObjC Source List with Static Values

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;

Applescript:

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.

Offline

 

#11 2019-08-15 02:53:50 pm

markconwaymunro
Member
Registered: 2017-11-28
Posts: 18

Re: ASObjC Source List with Static Values

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.

Offline

 

#12 2019-08-15 02:55:04 pm

markconwaymunro
Member
Registered: 2017-11-28
Posts: 18

Re: ASObjC Source List with Static Values

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.

Offline

 

#13 2019-08-15 03:20:27 pm

markconwaymunro
Member
Registered: 2017-11-28
Posts: 18

Re: ASObjC Source List with Static Values

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

Applescript:

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.

Offline

 

#14 2019-08-15 04:02:33 pm

TedW
Member
Registered: 2005-11-02
Posts: 20

Re: ASObjC Source List with Static Values

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.

Offline

 

#15 2019-08-15 04:36:33 pm

markconwaymunro
Member
Registered: 2017-11-28
Posts: 18

Re: ASObjC Source List with Static Values

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.

Offline

 

#16 2019-08-15 07:33:53 pm

markconwaymunro
Member
Registered: 2017-11-28
Posts: 18

Re: ASObjC Source List with Static Values

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

Offline

 

#17 2019-08-15 08:47:48 pm

TedW
Member
Registered: 2005-11-02
Posts: 20

Re: ASObjC Source List with Static Values

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.

Last edited by TedW (2019-08-15 08:55:07 pm)

Offline

 

#18 2019-08-17 01:43:52 pm

TheIaMonLyOneHeRe
Member
Registered: 2018-09-16
Posts: 6

Re: ASObjC Source List with Static Values

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?

Offline

 

#19 2019-08-17 11:26:45 pm

TedW
Member
Registered: 2005-11-02
Posts: 20

Re: ASObjC Source List with Static Values

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... big_smile

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.

Offline

 

#20 Yesterday 07:44:05 am

TheIaMonLyOneHeRe
Member
Registered: 2018-09-16
Posts: 6

Re: ASObjC Source List with Static Values

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.

Offline

 

#21 Yesterday 10:46:29 pm

TedW
Member
Registered: 2005-11-02
Posts: 20

Re: ASObjC Source List with Static Values

Ok, so here's how you use a nib file (at least one way of doing it, but it's the one that makes most sense to me...).

First, change the model. In the AppDelegate file's 'applicationWillFinishLaunching:' handler, change the list of dictionaries to add a new value "nibName" that holds the name of the nib we want to invoke. It should look something like this:

Applescript:

set sidebarList to {{title:"Header 1", isHeader:true, nibName:""}, {title:"Line 1", isHeader:false, nibName:"Line1"}, {title:"Header 2", isHeader:true, nibName:""}, {title:"Line 2", isHeader:false, nibName:"Line2"}, {title:"Line 3", isHeader:false, nibName:"Line3"}, {title:"Header 3", isHeader:true, nibName:""}, {title:"Line 4", isHeader:false, nibName:"Line4"}}

Next, create a view controller. Add a new blank file to the project: Choose New File, scroll down to the 'Other' section, choose 'Empty', and then click the 'Next' Button. Name the file "MyViewController.applescript" (or whatever makes sense to you, with a .applescript extension). Open this new file and edit in the following:

Applescript:

script MyViewController
   property parent: class "NSViewController"

   on initWithNibName:nibName bundle:bundle
       continue initWithNibName:nibName bundle:bundle
   end

   on loadView()
       continue loadView()
   end
end script

The parent class has to be NSViewController (it defualts, I think, to NSObject), the name of the script is the name of the class you'll invoke later, and you'll need to override at least these two methods explicitly. continue is the AppleScript equivalent of 'super', so all you're really doing here is telling the subclass to call the objective-C class implementations. You'll probably need to do on override like this for any method you call explicitly from AppleScript, should you decide to expand. 

Next, create your xib files. Choose New File, scroll down to the 'User Interface' section, choose 'View', and then click the 'Next' Button. Use the names in your data model (e.g., if a 'nibName' in the model is "Line2" you should have a name corresponding file Line2.xib; I believe Xcode will automatically add the xib extension). Construct the contents of the view as you see fit, then do this:

1. In the xib view, click on File's Owner
2. Click on the Identity inspector, and make sure the file's owner class is "MyViewController", or whatever you named your view controller class. It has to correspond to the first line of the file: "script MyViewController"
3. Click on the Connections inspector, and drag to connect the 'view' outlet of the File's Owner to the top-level of the view you've created.

Of course, here you'll have to do whatever bindings and connection you want to make for this particular view. Anything you set as a property in the view controller can be accessed by the view through the parent object, and that's how you'll pass data into the view. But I'm sure you can figure that out.

Now you're ready to go: back in the AppDelegate file, change the 'tableViewSelectionDidChange' like so (note again that I'm using the classname 'MyViewController', but you may end up changing that):

Applescript:

on tableViewSelectionDidChange:aNotification
   set tableView to aNotification's object
   set selectedRowIdx to (tableView's selectedRow) as integer
   set rowData to arrayController's arrangedObjects's objectAtIndex:selectedRowIdx
   set theNibName to (rowData's nibName) as text
   if theNibName is not "" then
       set viewController to current application's class "MyViewController"'s alloc's initWithNibName:(rowData's nibName) bundle:(current application's NSBundle's mainBundle)
       viewController's loadView()
       set theView to viewController's view
       set theView'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|:theView
       else
           detailView's addSubview:theView
       end

       (* set view constraints *)
       set constraintLeading to theView's leadingAnchor's constraintEqualToAnchor:(detailView's leadingAnchor)
       set constraintTop to theView's topAnchor's constraintEqualToAnchor:(detailView's topAnchor)
       set constraintBottom to theView's bottomAnchor's constraintEqualToAnchor:(detailView's bottomAnchor)
       set constraintTrailing to theView's trailingAnchor's constraintEqualToAnchor:(detailView's trailingAnchor)
       constraintLeading's setActive:true
       constraintTop's setActive:true
       constraintBottom's setActive:true
       constraintTrailing's setActive:true
end if
end

All this does is create a view for the appropriate xib, load its view, and then add and constrain that view to the detailView, as before.

Just so it's said, best practice would really be to eliminate the detailView object and use an NSLayoutGuide instead, but this approach strikes me as more accessible.

Offline

 

#22 Yesterday 11:14:13 pm

Shane Stanley
Member
From:: Australia
Registered: 2002-12-07
Posts: 5825

Re: ASObjC Source List with Static Values

TedW wrote:

Ok, so here's how you use a nib file (at least one way of doing it, but it's the one that makes most sense to me...).



I suspect you're doing it the hard way. Create the Nib in Interface Builder as normal, and set its owner to the owner of the table's nib. Make any connections you need there. Somewhere early in your code run this:

Applescript:

set cell1Nib to current application's NSNib's initWithNibNamed:"<name of your nib>" bundle:(missing value)
myTableView's registerNib:call1Nib forIdentifier:"<some identifier>"

Then you can use that identifier in your tableView:viewForTableColumn:row: handler, and make any changes you need at that point.


Shane Stanley <sstanley@myriad-com.com.au>
www.macosxautomation.com/applescript/apps/
latenightsw.com

Offline

 

#23 Today 12:43:21 am

TedW
Member
Registered: 2005-11-02
Posts: 20

Re: ASObjC Source List with Static Values

Shane Stanley wrote:

Then you can use that identifier in your tableView:viewForTableColumn:row: handler, and make any changes you need at that point.



Well, except these views are for the detail view, not part of the table view itself. It still might be a better approach; I think I saw you use this approach in some other thread at one point or another, and it crossed my mind to use it, but I'm just so $%^*#$ used to using view controllers that I balked. roll Maybe tomorrow I'll edit in a PS with your suggestion, just to see how it feels.

Yours would certainly be a better approach for adding custom table cell views, but neither Mark nor TheIaMon seems to want to go beyond the default image/textfield view.

Offline

 

#24 Today 03:02:52 pm

TheIaMonLyOneHeRe
Member
Registered: 2018-09-16
Posts: 6

Re: ASObjC Source List with Static Values

thanks Ted, I'll try something later and let you know the results

Offline

 

#25 Today 03:52:25 pm

TedW
Member
Registered: 2005-11-02
Posts: 20

Re: ASObjC Source List with Static Values

Shane Stanley wrote:

I suspect you're doing it the hard way[...]



Well, I looked into it, and you're right, we can skip the view controller entirely if we want, using this:

Applescript:

-- IBOutlet for the xib's view
property view:(missing value)

[...]

on tableViewSelectionDidChange:aNotification
[...]
   if theNibName is not "" then
       set theNib to current application's NSNib's alloc's initWithNibNamed:theNibName bundle:(missing value)
       theNib's instantiateWithOwner:me topLevelObjects:(missing value)

(* the remainder is unchanged, except for pushing the view commands into a tell block *)
       set detailSubviews to (detailView's subviews) as list
       tell view
           set its translatesAutoresizingMaskIntoConstraints to false
           if detailSubviews is not {} then
               set oldview to item 1 of detailSubviews
               detailView's replaceSubview:oldView |with|:it
           else
               detailView's addSubview:it
           end

           set constraintLeading to its leadingAnchor's constraintEqualToAnchor:(detailView's leadingAnchor)
           set constraintTop to its topAnchor's constraintEqualToAnchor:(detailView's topAnchor)
           set constraintBottom to its bottomAnchor's constraintEqualToAnchor:(detailView's bottomAnchor)
           set constraintTrailing to its trailingAnchor's constraintEqualToAnchor:(detailView's trailingAnchor)
           constraintLeading's setActive:true
           constraintTop's setActive:true
           constraintBottom's setActive:true
           constraintTrailing's setActive:true
       end tell
   end if
end tableViewSelectionDidChange:

Whether this is advantageous or not would depend on how interactive the view is, I suppose: The more we have to interact with the view programmatically, the more properties and code we would have to cram into the app delegate script.

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)