NSOutlineView's noteNumberOfRowsChanged isn't equivalent to reloadData - cocoa

I add another Object to my NSOutlineView's NSOutlineViewDataSource and call [myOutlineView noteNumberOfRowsChanged]. If I read the documentation correctly this should get the OutlineView to realize I added one row and redraw itself properly. But instead nothing happens.
If I call [myOutlineView reloadData] it works, but according to the documentation reloadData has a much higher overhead than noteNumberOfRowsChanged (since it reloads all data ... duh)
I'm not using bindings, I supply NSOutlineViewDataSource myself.
Anyone know what I'm doing wrong?

From the reloadData: documentation:
If you just want to update the
scroller, use noteNumberOfRowsChanged;
if the height of a set of rows
changes, use
noteHeightOfRowsWithIndexesChanged:.
To me that sounds like that noteHeightOfRowsWithIndexesChanged: only ensures that the newly added object is visible within the view.
The discussion section of the reloadData: documentation also mentions a way to update a single row. But it seems that reloadData: is the only option if you add new objects.

Related

NSOutlineView reloadItem: has no effect

I'm trying to release some strain on a view-based NSOutlineView for which I changed a single item property and which I initially reloaded just fine using [myOutlineView reloadData].
I tried [myOutlineView reloadItem: myOutlineViewItem] but it never calls - (NSView *)outlineView:(NSOutlineView *)ov viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item and consequently the data is not updated.
-(void)reloadOutlineViewObject
{
//[myOutlineView reloadData]; //Reload data just fines but is ressource-hungry
NSLog(#"%d",[myOutlineView rowForItem:myOutlineViewItem]; //Making sure my object is an item of the outlineView, which it is !
[myOutlineView reloadItem:myOutlineViewItem];
}
Am I missing something here ?
UPDATE
As pointed out in the comments, my outlineView is view-based.
UPDATE 2
Trying out some stuffs made me realized that the object I am reloading is a second-level object (cf object tree) and calling reloadItem:firstLevelObject reloadChildren:YES does work.
Would it be possible that we can only call reloadItem: on first-level object ? That would be highly inefficient in my case (I only have one two level item and plenty of second level) !
nil ->firstLevelA ->secondLevel1
->secondLevel2
->firstLevelB ->secondLevel3
->secondLevel4
Gonna try to subclass NSOutlineView and rewrite reloadItem: in the mean time.
UPDATE 3
I took a look at NSOutlineView in Cocotron to get start and felt that the code I needed to write to overwrite reloadItem would be quiet heavy. Anyone to confirm ?
I encountered this same problem with a view-based outline view, where calling -reloadItem: seems to just not do anything. This definitely seems like a big bug, though the documentation doesn't explicitly say that reloadItem will reacquire the views for that row.
My workaround was to call NSTableView's -reloadDataForRowIndexes:columnIndexes: instead, which seems to work as expected, triggering a call to the -outlineView:viewForTableColumn:item: delegate method for just that item. You can get the row that needs to be reloaded by calling -rowForItem: and passing in the item you want to reload.
This really isn't a bug - it was something I had explicitly designed. My thought was that reloadItem (etc) should just reload the outline view item properties, not the table cell at that item, since it doesn't carry enough specific information on what to reload (such as what specific cell you might want reloaded). I had intended for people to use reloadDataForRowIndexes:columnIndexes: to reload a particular view based tableview cell; we usually don't provide cover methods when the base class can easily do the same thing with just a few lines of code.
However, having said that, I know multiple people have gotten confused about this, and most people expect it to reload the cell too.
Please log a bug requesting Apple to change this.
thanks,
-corbin
Apple seems to have "fixed" it.
WWDC 2016, presentation 203 "What's New in Cocoa" at 30:35 in the video:
"NSOutlineView
Reloads cell views associated with the 'item' when reloadItem() is called"
reloadItem: works only on macOS 10.12.
From release notes:
https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKit/
NSOutlineView will now reload the cell views associated with ‘item’
when [outlineView reloadItem:] is called. The method simply calls
[outlineView reloadDataForRowIndexes:columnIndexes:] passing the
particular row that is to be reloaded, and all the columns. For
compatibility, this will only work for applications that link against
the 10.12 SDK.
So, if you want to reload row on earlier systems, you should use -reloadDataForRowIndexes:columnIndexes:.
Something like that:
let index = outlineView.row(forItem: obj)
let rowIndex = IndexSet(integer: index)
let cols = IndexSet(0 ... outlineView.numberOfColumns)
outlineView.reloadData(forRowIndexes: rowIndex, columnIndexes: cols)

replaceSubview:with and ARC is releasing old view

I have an issue when using NSView's replaceSubview:with: method to swap out different views. The old view is released when the method is called, the docs state,
This method causes oldView to be released; if you plan to reuse it, be
sure to retain it before sending this message and to release it as
appropriate when adding it as a subview of another NSView.
However, when using automatic reference counting (ARC) retain messages cannot be sent. Do the docs need to be updated, how can I use this method with ARC?
The views I am swapping exist all in the same nib and I do not have different view controllers. What is the preferred way of swapping out views and storing them for later use?
First store the old view for later use in a strong variable and then swap it out should prevent it from being released.

RootViewController need to resort/reload data array xcode 4.3

I'm implementing an example, in that example, I read in data from a database, put it in an array, sort it, and it's displayed using the RootViewController. The DB read and array load happen before the RVC code. So, it works, I get the data in the window created by the RVC and there's a nav controller there as well.
I want to add a button or something to the nav controller so that when you hit it, it sends a value back to the RootViewController.m file, then based on that value, I want to resort the array and display it once again in the RootViewController window.
I'm not sure how to do this. What changes would I have to make to the .xib and the RootViewController.m file?
Please, I'm a confused nube. %-0 Thank you very much.
There's a fair amount to this, so I'll give some general points and if any of them cause problems, it may be easier to work out smaller details.
In you RVC's viewDidLoad method, you can create a button and set it as the right or left button in your controller's navigationItem.
You can associate a tap on that button with a method in your controller that can do whatever you want when the button is tapped. (But a button doesn't send values, really, so you may have to explain more about that idea.)
I assume the RVC has a table view if you're showing array contents, so once the array (mutable array, I'd assume) is re-sorted, you can tell the table view to reload its data.
In answer to your secondary question, once you have resorted your array (or generally updated your data however you wish) you can force the table view to reload programmatically
[tableView reloadData];
Where 'tableView' is your instance variable pointing to your table view

What is a NSBrowserTableView as compared to an NSBrowser?

I'm implementing a -(void)delete: method so I can handle the delete key in my Cocoa app. I want it to do different things depending on what's selected: for text-fields, I want the default behaviour (remove char to the left), but for NSBrowser items, I want it to delete the item.
I thought I would ask the Window for it's first responder, and then see if that first responder is equal to the pointer for my NSBrowser, but it never matched. When I debug it, I find that the firstResponder is pointing to an instance of NSBrowserTableView, but I can't find that in the documentation.
What is it?
And how else could I test to see if my firstResponder is a particular tableView? (I Thought of subclassing NSBrowser but I tend to avoid subclassing, and my second thought was to add a tag, but I like my first method best, if only the firstResponder would point to my NSBrowser instance when one of the items in the browser is selected. )
Thoughts?
Actually, #trudyscousin is only partially correct. This class is definitely not a subclass of NSBrowser.
NSBrowserTableView is a private subclass of NSTableView used by NSBrowser to display each column. The table view is used so there is a separate place to draw the branch image (the little arrow drawn next to folders) while leaving the rest of the row to be drawn by either the default or user-defined cell.
If you think about it, it actually makes sense that the table view (rather than the browser) be the first responder, because then the table for the active column gets first crack at responding to keystrokes, and NSBrowser can let NSTableView do what it already knows how to. (For example, jumping to the first row that matches a letter typed by the user.)
Fortunately, NSBrowserTableView has a pointer back to the browser it works for. You can access this via its -(NSBrowser*)browser method. I recommend you don't subclass NSBrowser for this particular case, since you'd have to have a deep knowledge of its private implementation to do anything useful.
You can't find that in the documentation because it's private. My guess is that, when you instantiate a NSBrowser or a NSTableView, you're actually instantiating a subclass of this private class, which itself is a subclass of NSControl (which is pointed out in the documentation as being the superclass of both NSBrowser and NSTableView). Another example is NSString represented as 'NSCFString,' which I take as an allusion to the fact that CFString and NSString are "toll-free bridged."
Take this with as many grains of salt as you wish, but the way I'd go about gaining insight into the first responder is inserting a NSLog statement in my code and breaking just beyond it, seeing what was printed in the log. You could set the view's tag and display that in the statement. Or you could ask for your first repsponder's class
NSStringFromClass([myFirstResponder class])
and display that.
Hope this helped.

How to get notifications of NSView isHidden changes?

I am building a Cocoa desktop application. I want to know when a NSView's isHidden status has changed. So far using target/action doesn't help, and I can't find anything in NSNotification for this task. I would like to avoid overriding the setHidden method, because then I'll have to override all the NSView derived class that I am using.
UPDATE: I ended up using KVO. The path for "isHidden" is "hidden", probably because the setter is "setHidden".
You could use Key-Value Observing to observe the isHidden property of the NSView(s). When you receive a change notification from one of these views, you can check if it or one of its superviews is hidden with -isHiddenOrHasHiddenAncestor.
A word of warning: getting Key-Value Observing right is slightly tricky. I would highly recommend reading this post by Michael Ash, or using the -[NSObject gtm_addObserver:forKeyPath:selector:userInfo:options] method from the NSObject+KeyValueObserving category from the Google Toolbox for Mac.
More generally, one can override viewWillMoveToWindow: or the other related methods in NSView to tell when a view will actually be showing (i.e. it's window is in the window display list AND the view is not hidden). Thus the dependency on KVO for the 'hidden' key used above is removed, which only works if setIsHidden has been called on that view. In the override, 'window' (or [self window]) will indicate whether the view is being put into a visible view hierarchy (window is non-nil) or being taken out of it (window is nil).
I use it for example to start/stop a timer to update a control from online data periodically - when I only want to update while the control is visible.
Could you override the setter method for the hidden property so that it will trigger some custom notification within your application?

Resources