Can't select rows in an NSTableView in Service app - objective-c

I'm literally going nuts.
I have a service app that opens a pretty simple NSPanel that consists of an NSTableView, a label, and three NSButton controls.
That's it.
The table view is view-based, and has four different NSTableCellView rows defined in IB.
The table code is pretty straight forward; my NSWindowController subclass is both the data source and the delegate. And for the most part, everything works perfectly:
the window opens
the expected data source methods (numberOfRowsInTableView: and
tableView:objectValueForTableColumn:row:) get called
the table gets populated with its views and section separators via
tableView:isGroupRow: and tableView:viewForTableColumn:row:
empty selections are disabled, so the delegate immediately receives a
tableView:shouldSelectRow: message, and the table automatically
selects the first row
the table appears exactly as it should and the first row is selected
Everything looks perfect, but I can't select a different rows. Nothing I click on in the table changes the selection.
I have tried:
subclassing NSTableCellView and verified that it is receiving a hitTest: call
tried "hacking" the table cell view hitTest: so it always return nil
tried various combination of "refuses first responder", "enabled", "editable" properties on the control view inside the table cell view
tried deleting all of the control views. so the table cell views were empty
tried implementing tableView:selectionIndexesForProposedSelection: instead of tableView:shouldSelectRow:
changed the NSPanel into a regular NSWindow
Nothing seems to make any difference. Nothing I click on in the table changes the selection, and my table view delegate never receives another tableView:shouldSelectRow: call.
Note that all of the other (NSButton) controls in the window work just fine. I can click on any of them.
Update #1
Per the comment, I tried changing the product to a plain-old .app, but it makes no difference.
My next step was to fiddle some more with the delegate methods and have narrowed down the problem to this:
If I implement tableView:shouldSelectRow:, the table calls my delegate method twice (because I have have "empty selection" turned off, the table must initially determine which row select as a default, so I get two calls, one for row 0 (NO) and a second for row 1 (YES)). However, if I click in the table, I never receive another tableView:shouldSelectRow:.
If I remove the implementation of tableView:shouldSelectRow:, table selection magically starts to work. (Except for the fact that you can select group rows, which is worng.)
Also tried implementing tableView:selectionIndexesForProposedSelection: instead of tableView:shouldSelectRow:; same behavior
So it appears if I implement any of the delegate methods to determine which rows are selectable, I can't select any rows. sigh
Update #2
I punted and refactored the app so there is now a new view controller object dedicated to managing the table view (rather than overloading the window controller). All data model and delegate methods were moved to the new view controller.
Also (thinking there might be something weird with the table view in IB), I deleted the table view from the NIB and recreated it and all of its connections.
Unfortunately, neither of these changes made any difference. If the tableView:shouldSelectRow: is implemented, the table is unusable. Delete the method, and it works again.
Update #3
And it just get weirder: thinking I could "hack" the row selection problem, I implemented the table view delegate methods tableViewSelectionIsChanging: and tableViewSelectionDidChange:. Neither get called. Even if I remove tableView:shouldSelectRow:, allowing the table selection to work, no tableViewSelectionIsChanging: or tableViewSelectionDidChange: is ever received.
Yet, if I add observers for NSTableViewSelectionIsChangingNotification and NSTableViewSelectionDidChangeNotification, those are received.
Also note that the NSViewController subclass that is the table view's delegate and data source explicitly conforms to <NSTableViewDelegate> and <NSTableViewDataSource>, so there's shouldn't be any reason why the table view should be confused as to what delegate methods are implemented.

Yikes! Now this is embarrassing.
So the problem was a weak reference(s) to the window controller.
Here's what was happening: the window controller was being created, loaded, and the window was presented. During the initial presentation and display, everything worked (tableView:shouldSelectRow: et. al.) because the window and view controllers existed. But in some future event loop, ARC destroyed the window and view controllers, leaving only the window on the screen with a table view, and the weak references to its delegate and data source objects were now nil.
Solution was to fix the window controller management so it keeps a strong reference to the window controller until the window closes.
Sometimes it's the simplest things that trip you up...

Related

Detect which NSTableView is active

I have an NSSplitView showing two NSTableView instances. I need to detect which table view has become "active" (of focused), which means the one that the user has clicked. I need to know that because each table view acts as a source list for another view that shows the content of the selected row(s). This other view is shared for both tables.
I could do it by subclassing NSTableView and reacting to mouseDown: or another method but it I'd rather avoid subclassing just for that. I also don't want to track any NSWindow event just to know if the user has clicked one of the tables (I'd rather subclass NSTableView).
Currently, I use the delegate method tableViewSelectionDidChange:, but this method is, obviously, only called when the selected row changes. I need to know that a table becomes active even if the selected row hasn't changed.
Observing the clickedRow property of the table views doesn't appear to work. If may not be KVO compliant.
Any ideas?
For those interested, the most convenient solution I found was to take advantage of the fact that NSTableView is a subclass of NSControl. So just like NSButton it can send action messages when clicked (upon mouse up).
For each tableView, I wired its "action" to the same ibaction selector of my controller object in interface builder.
The controller identifies the sender and acts accordingly.
No need to subclass NSTableView.

View-based NSTableView is only redrawn when focus is changed

I'm writing a simple Cocoa app with GUI that only consists of a simple table whose data may update sometimes. But when data actually updates, table's rows in most cases disappear completely. But as soon as I change focus (click on desktop if my app's a primary window or click on app's window if it's not), everything shows up properly
Data is handled in a separate singleton class that runs a thread that actually looks after data source and publishes NSNotification when data changes. Window controller receives that notification, extracts new data and triggers [tableView reloadData] - that's where the problems begin
Window controller is a data source and delegate for the table and implements numberOfRowsInTableView method and viewForTableColumn method. When the problem occurs, numberOfRowsInTableView is called and returns non-zero value, but viewForTableColumn isn't called at all
I expect the table to be properly redrawn whenever [tableView reloadData] is called and wherever my focus is, but on practice everything just disappears and properly redrawn only after I change focus
Fixed it. The problem was updating GUI from background thread

When does table view gets created?

I have trouble in understanding when does table
View gets created.
I have two tab view. In second tab view I have table whose datasource is been
Adhered to the table view protocol.
Whenever the second tab is selected /clicked the tables datasource method
numberOfRowsInTableView is called on every click.
I'm using xcode 4.2.
Is that because each tab view recreates its view when the tab selected?
Am I missing something here?
I have trouble in understanding when does table View gets created.
If it's in a nib, it's created when you load the nib.
Actually, it depends on what you mean by “created”. You created it when you put the table view into the nib in Xcode. When you save, Xcode archives that object into the nib.
Then, at run time, when your app loads the nib, Cocoa unarchives the table view (along with everything else in the nib). That unarchiving is the moment of “creation” after which the table view exists in your app.
Whenever the second tab is selected /clicked the tables datasource method numberOfRowsInTableView is called on every click.
The table view already exists by then. It sends that message (among other data source messages) whenever it becomes visible, whether or not it's becoming visible for the first time.
If you switch to a different tab, the table view has no reason to show anything, so it won't bother following updates to the model.
If you then switch back, making the table view visible, now the table view has a reason to show something, so it needs to know if anything has changed so it can show the model's current state. So, it rechecks its data source at that time.
It depends if the content of TableView is dynamic or static. If it's dynamic it's created when the method tableView:cellForRowAtIndexPath:called. If the content is static it's created when loading the view. Note that tableView:cellForRowAtIndexPath: is called more than one time, it gets called as the count of the datasource items. Hope that helps :)

Is there a better way to load View Controllers from a table view?

This might be a very basic question, but I could not find the answer yet.
I have a UITableView that acts as a menu for my app. Each row on the table view, when selected, opens a different subclass of UIViewController.
At the moment my code works the same way used in the UICatlog example from Apple.
In the main view controller (the table view), each menu item is described in a dictionary in an array (menuList). Each dictionary contains an instance of the UIViewController subclass for that screen and other data about the menu item. When the user selects a row, the didSelectRow atIndexPath kicks in and calls the appropriate view controller, stored in the dictionary at that indexPath.row of the menuList array.
It seems to be very wasteful to alloc and init every single view controller when the table view first loads.
My question is: Is there a better way than the one demonstrated in UICatalog to alloc/init my view controller sublasses only when the associated row is tapped?
(I know I can use a complex if..else structure in the didSelectRow, but this results in an extremely long didSelectRow method and breaks encapsulation. I wonder if there is a cleaner way to do this, allocing and initing the appropriate view controller based on data from the dictionaries)
user1349768 try to use Storyboard, but this feature only works in iOs 4 and higher.
Just a suggestion ... put some reference to each view controller into NSArray and then initiate and segue to them when the row gets tapped on (and just get the reference from objectAtIndex:).
Although I could not find a better way to do this, the memory signature of each allocated View Controller is only 288 bytes. Since the solution suggested by apple is a lot more elegant and scalable then using a switch case statement, I left it as it is.

Who calls commitEditingStyle:forRowAtIndexPath?

I've got a custom UITableViewCell that allows a user to input text when in edit mode. I've noticed that on stock UITableViewCells, when I swipe left, then hit the Delete button, it's table view receives the commitEditingStyle:forRowAtIndexPath message. I'm curious where this message is coming from. As far as I can tell, individual cells don't have a reference to the table view they belong to. If I want my custom cell to send this message, how would I go about doing that? I've thought about using the superview of the cell, but it seems like there's got to be an easier way.
This method gets called automatically (if you implement the data source protocol) before adding any row or column.
If you want to know to which table view a cell is belonging, use the superview method.