NSTableBackgroundView - macos

I have a ViewController which has a single TableView within its content.
Within the ViewController I access the actual Table via:
NSView * myView = self.view.subviews[0];
myView = myView.subviews[0];
NSTableView *myTable = myView.subviews[0];
[self.targets sortUsingDescriptors: [myTable sortDescriptors]];
[myTable reloadData];
This actually is clumsy but seems to work. Except that when I mouse down within the table when the vertical scroll bars are active. Then, I raise an exception because in the final line of accessing subviews to reach myTable, the subview array has several NSTableBackgroundViews before the actual TableView that I created and want to access.
NSTableBackgroundView apparently is not documented and does not have a sortDescriptors method (hence the exception).
Perhaps someone can point out a better way to get to the table which is more reliable, or can someone say whether enumerating the final subview array to find the desired ClassOf NSTableView is safe?
Many Thanks!

in lieu of any other answers, I implemented planB, I took the array of subviews and enumerated on them to find the actual NSTableView. This seems to work when I bang on the UI to test it and no more flags are raised.
I wish there were a cleaner way I could discover to get the view I want without traversing all the subviews.

Related

NSTableView: get notified when floating subview gets removed

I have a custom NSTableView with floating group rows. These rows behave differently withing drawRect, according to whenever they are actually floating, or not. (There is only 1 floating row in an NSTableView at a time, that's the table's default behaviur)
I can track when they start floating, with a custom NSScrollView subclass, with obseverable override for the addFloatingSubview:forAxis: method.
I can't track when they stop floating though.
The NSScrollView documentation tells me:
You are responsible for keeping track of the floating views and removing them via removeFromSuperview when they should no longer float.
But it seems NSTableView does not call removeFromSuperview neither on (my custom subclass of) NSTableRowView; nor on the NSView used as a "cell" for the group row.
How to get notified when the NSTableView stops floating a subview?
I've found it: NSTableRowView.floating
NSTableView has so many subcomponents, I just did not seek for this on the right/trivial place :)

NSTableView displaying incorrectly in NSSplitView

I have an NStableView embedded in an NSSplitview.
The table will display, but when it does, the first three or so rows are not visible until I reize the window and/or split view. Then, it will snap into place and function perfectly fine until I quit.
Has this ever happened to anyone? Is there a simple method I can call on the view or table to get it to redraw?
This is how it displays when the view is first loaded (note: the user can scroll the table up and see the top row highlighted, but never get to it)
after resizing the window, the table view suddenly snaps into place and appears as it should:
You could try a [_yourSplitView display] to force a redraw of the NSSplitView. If I remeber correctly the SplitView will redraw all its subviews.
Try experimenting with where you use this, as result may vary depending on where in the init order you call this.
I actually got this working by calling the subview and then just resetting the position of the splitview divider.
NSView *v = [vc view];
[self.superDisplayView addSubview:v];
[self.SourceListSplitView setPosition:250 ofDividerAtIndex:0];

How can I tell programmatically whether a NSTableView is view-based or cell-based?

Should be an easy question, but there's nothing in the interface.
Apart from seeing whether something like preparedCellAtColumn:row: throws an exception, is there anything else one can do?
Except that preparedCellAtColumn:row: doesn't throw an exception, it just logs a message, and returns an NSCell object, so you can't test it for nil.
Funny that several people say that there’s no need to know when the questioner obviously does have that need. And there are good reasons you might want to know this; e.g. if you implement a generic NSTableView subclass or delegate, you must differentiate its behavior dependent on whether the table view is view- or cell-based.
If you use an NSArrayController and bindings, an easy way is to check for NSTableColumn bindings, because cell-based NSTableViews do have these, and view-based NSTableViews do not. So this code fragment will work:
NSTableColumn *tableColumn = [[myTableView tableColumns] objectAtIndex:0];
NSDictionary *binding = [tableColumn infoForBinding:#"value"];
if (binding) {...} // cell-based table view
else {...} // view-based table view
I haven’t tried myself, but if you use an NSTableViewDataSource instead, you’d probably simply check whether the data source responds to - tableView:setObjectValue:forTableColumn:row: (cell-based table view) or not (view-based table view).
There is no need to tell it, if you use objectValueForTableColumn it will automatically become cell based and on the other side if you use viewForTableColumn then it will be view based. You can pass any type of view in both of these methods.

textShouldEndEditing does not get called in NSTableView

When a user adds a new managed object, it shows up in a table, which scrolls down to the new entry, and the name of the new object (a default value) goes into editing mode.
I need to check if the name of the new object is unique in the datastore, so I can't use a formatter for this. I think the perfect moment where I should validate this, is whenever the user tries to commit the entry's name value, using textShouldEndEditing:.
I subclassed NSTableView and overrid following methods, just to be able to check in the log if they get called.
- (BOOL)textShouldEndEditing:(NSText *)textObject {
NSLog(#"textSHOULDendEditing fired in MyTableView");
return [super textShouldEndEditing:textObject];
}
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor {
NSLog(#"control:textShouldEndEditing fired in MyTableView");
return YES;
}
- (void)textDidEndEditing:(NSNotification *)aNotification {
NSLog(#"textDIDEndEditing fired in MyTableView");
}
textDidEndEditing: gets called fine, but textShouldEndEditing: does not.
In the NSTableView Class Reference, under Text Delegate Methods, both methods textShouldEndEditing: and textDidEndEditing: are listed. Someone please explain why one gets called and the other doesn't.
I think the NSTableView acts as the delegate for an NSTextField that gets instantiated as a black box delegate for the NSTextFieldCell. So what is referred to as delegate methods in the NSTableView Class Reference, actually implement the text manipulating methods for the NSTextField object.
I tried to declare the NSTextFieldCell as an outlet in my NSTableView. I also tried to declare several protocols in the NSTableView.
#import <AppKit/AppKit.h>
#import <Cocoa/Cocoa.h>
#interface MyTableView : NSTableView <NSTextDelegate, NSTextFieldDelegate, NSControlTextEditingDelegate, NSTableViewDelegate, NSTableViewDataSource> {
}
#end
Don't laugh, I even tried to declare my table view as its own delegate :P
After banging my head one entire day on this issue without finding any conclusive answer in Apple documentation, I decided to share the solution I've found in case somebody else struggles with the same problem.
According to the documentation, as the original poster mentioned, the methods control:textShouldBeginEditing and control:textShouldEndEditing of NSControlTextEditingDelegate should be called directly on the delegate:
This message is sent by the control directly to its delegate object.
Furthermore, a Technical Q&A was issued by Apple with the title Detecting the start and end edit sessions of a cell in NSTableView where it's clearly stated the following:
A: How do I detect start and end edit sessions of a cell in NSTableView?
In order to detect when a user is about to start and end an edit session of a cell in NSTableView, you need to be set as the delegate of that table and implement the following NSControl delegate methods:
- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor;
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor;
The table forwards the delegate message it is getting from the text view on to your delegate object using the control:textShouldEndEditing: method. This way your delegate can be informed of which control the text view field editor is acting on its behalf.
I found nothing in Apple's documentation stating anything different and if someone does, a documentation pointer would really be appreciated.
In fact, this appears to be true if a cell-based NSTableView is being used. But as soon as you change the table to a view-based table, the delegate method is not called any longer on the table delegate object.
A Solution
However, some heuristic tests I performed showed that those delegate methods get called on a view-based table delegate if (and as far as I know: and only if):
The table delegate is set.
The delegate of the editable control is set.
If you remove either delegate, the methods of the NSControlTextEditingDelegate protocol will not be called.
What's unexpected according to the (only) documentation is setting the delegate of the editable control. On the other hand setting the delegate object to receive delegate notifications sounds rather intuitive to me, and that's why I tried in the first place. But there's a catch! The curious thing, though, is that that's not sufficient. If the table delegate is removed, the NSControlTextEditingDelegate methods will not be called even if the delegate of the editable control is set (which is the weirdest thing to me).
Hope this helps somebody else not to lose time on this issue.
in your question you mention the insertion of a "managed object" and that was the problem. It seems that you are using a view based table, but the textShouldEndEditing: method only gets called for cell based tables.
I overrid -(void)awakeFromInsert; in the (subclassed) managed object, to construct a unique default value for the name-property.
Also, I ended up not overriding the -(BOOL)textShouldEndEditing: method in the table view. Instead, I check if a newly entered name-property is unique in the (subclassed) managed object's -(BOOL)validate<Key>:error:.
Together, the above two strategies result in unique name-properties in all managed objects.
Maybe I could have forced the NSTextFieldCell to go into editing mode, resulting in -(BOOL)textShouldEndEditing: to get called every time.
Some remarks though:
It seems -(BOOL)textShouldEndEditing: returns NO when the -(BOOL)validate<Key>:error: returns NO.
Both -(BOOL)textShouldEndEditing: and -(BOOL)validate<Key>:error: methods are called only when the user actually makes changes to the property.

How should I remove all items from an NSTableView controlled by NSArrayController?

I'm using an NSArrayController, NSMutableArray and NSTableView to show a list of my own custom objects (although this question probably applies if you're just showing a list of vanilla NSString objects too).
At various points in time, I need to clear out my array and refresh the data from my data source. However, just calling removeAllObjects on my NSMutableArray object does not trigger the KVO updates, so the list on screen remains unchanged.
NSArrayController has no removeAllObjects method available, which seems really weird. (It does have addObject, which I use to add the objects, ensuring the KVO is triggered and the UI is updated.)
The cleanest way I've managed to cause this happen correctly is:
[self willChangeValueForKey:#"myArray"];
[myArray removeAllObjects];
[self didChangeValueForKey:#"myArray"];
...so I'm kind of having to do the KVO notification manually myself (this is in my test app class, that contains the myArray property, which is NSMutableArray, as mentioned.)
This seems wrong - is there a better way? From my googling it seems a few people are confused by the lack of removeAllObjects in NSArrayController, but haven't seen any better solutions.
I have seen this solution:
[self removeObjectsAtArrangedObjectIndexes:
[NSIndexSet indexSetWithIndexesInRange:
NSMakeRange(0, [[self arrangedObjects] count])]];
but this looks even more unpleasant to me. At least my solution is at least marginally self-documenting.
Did Apple not notice that sometimes people might want to empty a list control being managed via an NSArrayController object? This seems kind of obvious, so I think I must be missing something...
Aside: of course, if I add new items to the array (via NSArrayController), then this triggers a KVO update with the NSArrayController/NSTableView, but:
Sometimes I don't put any items in the list, because there are none. So you just see the old items.
This is a bit yucky anyway.
You don't remove items from a table view. It doesn't have any items—it just displays another object's items.
If you bound the array controller's content array binding to an array property of some other object, then you should be working with that property of that object. Use [[object mutableArrayValueForKey:#"property"] removeAllObjects].
If, on the other hand, you haven't bound the array controller's content array binding, then you need to interact with its content directly. Use [[arrayController mutableArrayValueForKey:#"content"] removeAllObjects]. (You could also work with arrangedObjects instead of content. If one doesn't work, try the other—I've only ever done things the first way, binding the array controller to something else.)
Had this problem as well and solved it this way:
NSArrayController* persons = /* your array controller */;
[[persons content] removeAllObjects];
Swift
#IBOutlet var acLogs: NSArrayController!
acLogs.removeObjects(acLogs.content as! [AnyObject])
worked for me.
Solution in Swift:
if let ac = arrayController
{
let range:NSRange = NSMakeRange(0, ac.arrangedObjects.count);
let indexSet:NSIndexSet = NSIndexSet(indexesInRange: range);
ac.removeObjectsAtArrangedObjectIndexes(indexSet);
}
Just an update that works in Swift 4:
let range = 0 ..< (self.arrayController.arrangedObjects as AnyObject).count
self.arrayController.remove(atArrangedObjectIndexes: IndexSet(integersIn: range))

Resources