NSTableView: Renaming one row results in renaming two rows at once - sorting

My Setup: I have a cell based NSTableview. The columns are bound to values of an NSArrayController (key-value-binding in Interface Builder). The NSArrayController contains a set of NSManagedObjects (CoreData entity (Device)). The tableview's sortDescriptor is bound the the one of array controller.
My Issue: When I sort the tableview by name and then rename a row, the NSArrayController rearranges it's content and the nstableview changes the order of the rows. I want that. However, instead of just renaming one row, it renames two rows. This only happens if the order of the rows has to change because of its sort descriptor.
I.e. The table view looks like the left row below. If I rename f to b it changes to the row on the right
a -> a
c -> b
d -> b
e -> c
f -> d
I set a breakpoint at the setter of the name property to see what sets the name a second time. However I don't get much smarter by looking at the stack.
This is the first time the setter is being called:
This is the second time:
I have build a sample app, however I cannot reproduce this behavior. I must be doing something wrong somewhere, but I can't find what. Tried everything all day long. Does anyone have an idea, what is going wrong here? Thanks :)

I have found the source of the issue:
I am not sure what the exact reason ist, but there were two rings I did wrong.
I bound the NSTableView's content property the NSArrayController's arrangedObjects. I removed that. That was when I noticed something else must be wrong, since now no data at all has been visible in the NSTableView:
The NSArrayController was not yet ready when the NSTableView was
being setup with the bindings. So the columns have been bound to
nil. I am now binding the column values programmatically to the NSArrayController's arrangedObjects as soon as arrangedObjects is ready.

Related

NSArrayController, View Based Table, NSPopUpButton and Bindings

I am trying to bind a NSPopUpButton in a view based NSTableView with NSArrayContollers using Xcode 8.1. I have an macOS app that had been using a cell based NSTableView and I would like to convert it to a view based table, however, I have been completely unsuccessful in doing this. I have looked at all the various wed postings, but nothing seems to work for me. I have been working on this for over two days.
Here is my design:
accountArrayController is bound to the larger table and this all seems to work just fine.
patientArrayController is aNSMutableArray of NSString's that contains the list of patients to be populated in the menu items of the NSPopUpButton.
Here are the actual bindings for the NSPopUpButton
I am guessing that the problem is in the Content Values bindings, I have tried many variations. When I compile this, I get
.../xxx.storyboard: Exception while running ibtool: *** -[__NSArrayM insertObject:atIndex:]: object cannot be nil
However, when I try other variations, Xcode (really ibtool) hangs for a long time and then exits with an error code of 255.
How can I resolve this? I'm happy to provide other binding information and code blocks, if needed.
The model attribute name you used in the NSTableColumn bind, you want to also use in the Table Cell View bind - 3rd level down from table column. It should have filled in already 'objectValue' or 'selection' or 'arrangedObjects', just add . and you should be good.

Cocoa Bound NSTableView (or NSOutlineView) Cell for "No Data"

Just wondering how I would achieve getting my NSOutlineView (or even a NSTableView) to display a cell (row) based on "no rows" from the Cocoa bound NSArrayController.
At the moment I have an NSOutlineView bound to an NSArrayController (I should really use a NSTreeController, but just playing around.)..
The bindings and working fine in terms of the data being bound (from CoreData). Using various sortDescriptors, it further sorts into alphabetical groups (much like the contacts app)...
When I'm trying to do is get my third cell, the one denoting "no data" / "no entries" to appear when there's nothing from the bindings.
Ay pointers would be handy.
Thanks in advance.
Ade
One solution is an extra view over the outlineview. The view is visible if the number of rows in the array/treecontroller is 0.

Pre-creating UICollectionView cells - layout issue

I have a UITableView with 6 rows. Each row contains a single UICollectionView. Each collection view contains a single section with 10-15 cells. One view controller is the datasource and delegate for both the table view and the collection view.
I would like to address some performance issues when scrolling the tableview. Whenever a new section comes into view, there is a small pause while the collection view is created and filled. Since I have a fixed number of cells (< 100) and they are almost static (they are initially loaded from a web API but the data will change only a couple of times a week), I would like to pre-build each of the collection view cells in advance. I would prefer the user waits an extra half-second on launch than encounters jerky scrolling.
To accomplish this, I have updated my collectionView: cellForItemAtIndexPath: to check a dictionary of cells I am maintaining. It looks for a key composited from the collection view index and the indexPath for the cell. If the key exists, the corresponding object is returned. If none is found, the cell is built and also added to the dictionary. This step effectively prevents cells from being un-loaded and recycled, at the expense of using more memory.
But on launch, I still need to run this once for each cell to pre-populate the dictionary. I iterate over each table view cell, find the collection view, and call
[self collectionView:collectionView cellForItemAtIndexPath:indexPath];
This is almost enough. The cells are being created and stored in the dictionary, and when I scroll to a new collection view, I see that they are being pulled from the dictionary and are displayed. But all of the cells, and all of their contents, are shoved up in the top-left corner at {0,0}.
Some logging tells me that at the time the cells are created, the frame of the collection view is {{0, 0}, {0, 0}}. I assume this is why none of my layout is being applied?
How can I resolve this?
(Would also be interested in any general comments on my pre-loading implementation).
I resolved this by calling [cell layoutIfNeeded] on the UITableViewCell (not the collection view). A more thorough explanation is welcomed.

Binding a NSArrayController to a NSPopupButton & NSTextField

What I want to accomplish seems like it should be fairly straightforward. I have placed a sample project here.
I have a NSArrayController filled with an array of NSDictionaries.
[[self controller] addObject:#{ #"name" : #"itemA", #"part" : #"partA" }];
[[self controller] addObject:#{ #"name" : #"itemB", #"part" : #"partB" }];
[[self controller] addObject:#{ #"name" : #"itemC", #"part" : #"partC" }];
I am populating a NSPopupButton with the items in this array based on the 'name' key. This is easily accomplished with the following bindings
I would then like to populate a NSTextField with the text in the 'part' key based on the current selection of the NSPopupButton. I have setup the following binding:
With these bindings alone, the text field does display 'partC'.
However, if I change the value of the NSPopupMenu, what the text field shows does not change.
I thought this would simply be a matter of setting up the 'Selected Object' binding on the NSPopupButton
but that isn't working. I end up with the proxy object in my menu for some strange reason (providing the reason why would be a bonus).
So, what do I need to do to make this work?
Don't use "Selected Object" in this case. Bind the pop-up's "Selected Index" binding to the NSArrayController's selectionIndex Controller Key. Tried it out on your sample project and it works.
EDIT:
You asked why it's appropriate to use selectionIndex over selectedObject. First some background:
When binding a popup menu, there are three virtual "Collections" you can bind: Content is the abstract "list of things that should be in the menu" -- you must always specify Content. If you specify neither Content Objects, nor Content Values, then the collection of values bound to Content will be used as the "objects" and the strings returned by their -description methods will be used as the "values". In other words, the Content Values are the strings displayed in the pop-up and the Content Objects are the things they correspond to (which are possibly not strings, and which might not have a -description method suitable for generating the text in the pop-up). What's important to realize here is that there are potentially three different 'virtual arrays' in play here: The array for Content, the array for Content Objects (which may be different) and the array for Content Values (which may also be different). They will all have the same number of values, and typically, the Content Objects and Content Values will be functions (in the mathematical sense) of the corresponding items in the Content array.
The next thing that's important to realize is that part of NSArrayController's purpose in life is to keep track of the user's selection. This is only mildly (if at all) interesting in the case of a pop-up, but starts to become far more interesting in the case of an NSTableView. Internally, NSArrayController keeps track of this by keeping an NSIndexSet containing the indexes in the Content array that are selected at any given time. From there, selection state is exposed in several different ways for your convenience:
selectionIndexes is as described - an NSIndexSet containing the indexes of the selected items in the Content array
selectionIndex is a convenient option for applications that do not support multiple selection. It can be thought of as being equivalent to arrayController.selectionIndexes.firstIndex.
selectedObject is also useful in single selection cases, and corresponds conceptually to ContentObjectsArray[arrayController.selectionIndexes.firstIndex]
selection returns a special object (opaque to the consumer) that brokers reads and writes back to the underlying object (or objects in the case of multiple selection) in the Content Array of the array controller. It exists to enable editing multiple objects at a time in multiple selection cases, and to provide support for other special cases. (You should think of this property as read-only; Since its type is opaque to the consumer, you could never make a suitable new value to write into it. It's meaningful to make calls like: -[arrayController.selection setValue: myObject forKey: #"modelKey"], but it's not meaningful to make calls like -[arrayController setValue: myObject forKey: #"selection"]
With that understanding of the selection property, let's take a step back and see why it's not the right thing to use in this case. NSPopUpButton tries to be smart: You've provided it with a list of things that should be in the menu via the Content and Content Values bindings. Then you've additionally told it that you want to bind its Selected Object to the the NSArrayController's selection property. You're probably thinking of this as a "write only" binding -- i.e. "Dear pop-up, please take the user's selection and push it into the arrayController", but the binding is really bi-directional. So when the bindings refresh, the popup first populates the menu with all the items from the Content/Content Values bindings, and then it says, "Oh, you say the value at arrayController.selection is my Selected Object. That's odd -- it's not in the list of things bound with my Content/Content Values bindings. I'd better add it to the list for you! I'll do that by calling -description on it, and plunking that string into the menu for you." But the object you get from that Selected Object binding is the opaque selection object described above (and you can see from the outcome that it is of class _NSControllerObjectProxy, a private-to-AppKit class as hinted by the leading underscore).
In sum, that's why binding your popup's Selected Object binding to the array controller's selection controller key is the wrong thing to do here. Sad to say, but as I'm sure you've discovered, the documentation for Cocoa bindings only begins to scratch the surface, so don't feel bad. I've been working with Cocoa bindings pretty much daily, in a large-scale project, for several years now, and I still feel like there are a lot of use cases I don't yet fully understand.

Search resets NSArrayController selection to none

I've got an NSTableView bound to an NSArrayController via content and selection indexes. All great so far - content displayed, etc.
Now an NSSearchField is bound to the array controller via filterPredicate and the property of the array content instances that's to be searched.
Searching/filtering the table view works great; table view showing only matching entries.
However, searching resets the selection on the NSTableView if the existing selection isn't in the search results. Worse, not only during the search but after ending the search there's no selection on the table view.
The NSArrayController is set up to Avoid Empty Selection.
Yet, debugging the array controller's selection indexes shows that searching resets them to an empty set. Don't quite know what to make of it..
Any hints on how to properly configure bindings in this scenario to really prevent an empty selection much appreciated!
Unfortunately array controllers don't track and restore the selection as their arranged objects change. You'll have to do this yourself in code. You can keep track of the current selection by using KVO to observe the selection on the array controller. You can also observe the controller's arranged objects to know when it changes as a result of filtering. Upon every change just set the current selection back to the tracked value (assuming its still in arranged objects) or set the selection to a new value.

Resources