View-based NSTableView is only redrawn when focus is changed - objective-c

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

Related

Can't select rows in an NSTableView in Service app

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

MKMapView blocks main thread while used in UITableView

I have a basic UITableView that contains 20 cells, one of the cells is a MKMapView just dropped in as is without any custom code (not even setRegion:Animated), if I open the view at the first time and scroll down the table view (towards the map's cell), there is a noticeable hang happens for the app, and if I use setRegion:Animated (without dropping any pin) the hang gets longer. However, this block of the main thread disappears on later attempts to scroll towards the map's cell.
I can't use the MKSnapShotter because I want the user to interact with the map so an image won't satisfy the case.
the table view does not make any block on the main thread if the map does not exist.
how to avoid blocking the main thread while showing the map's cell for the first time ?
First make a tableView IB outlet if you haven't already done so. Create the cell in view did load using tableView.dequeueReusableCellWithIdentifier("mapCellIdentifier") and store that in an ivar. Then in the data source return the cell when the index path is the one you want so inside of cellForRowAtIndexPath do
if (indexPath.row == wantedRow) {
return mapCell
}
The mapCellIdentifier needs to be set in the cell you created in interface builder for the map. This is assuming you have dynamic cells.

Xcode: Reloading a portion of controller with each view

I am working on an IPhone app that, through viewDidLoad, makes a connection and pulls data into a table. I would like the controller, or at least the table, to reload every time the controller is displayed, even if someone just switched from one tab to another or closed and re-opened the app on this controller. Is there a way to do this? I can't seem to find anything, it's also kind of a hard thing to search for. I have found discussions of when to put code in viewDidLoad but not another method that is run every them the controller displays.
Thanks,
Cheryl
You want to use
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
//code to reload table viewdata
}
Each time the view appears this method is called. I use this a lot in my app.
There's no way to do it by injecting code in only one place.
You can think of a workaround, like for example sending custom "reload request" notification from required parts of code:
viewDidAppear: in your view controller
applicationDidBecomeActive: (if table is presented)
tabBarController:didSelectViewController: (if switched onto controller with the table)
tabBar:didSelectItem: (same as above)
etc.
In you view controller simply observe this notification and reload data when required.
Although, whats more important: do you really need to reload data under such harsh requirements? In most cases data reload happens when
view controller's viewDidLoad: is called
it is manually initiated (button, for example)
long time passed since the last update was received
Otherwise it's just an overkill.

Loading UIViews in the background on application launch

I have a simple iPad application with 5 views. On the first view, the user is asked to make some selections and set some options. From this information, the other 4 views are programatically changed after an NSNotification message is sent to them. (i.e controls are added, updated).
My problem is that when the application is first loaded, the user sees View1, but View2, View3, View4 and View5 have never been opened yet, so any changes I make programatically to those views are not done and when the user navigates to them (via the tab bar) for the first time, no changes are shown.
[EDIT: I should point out that the code for making the changes to each view is contained within the ViewController itself, and is executed when the view observes the incoming NSNotification. When the view is not loaded, it understandably never received the incoming NSNotification.]
Only after the user looks at any of those screens at least once and then goes back to View1 and makes changes, are the other Views updated properly.
I thought I could get around this issue by actively loading Views 2,3,4 and 5 into memory on application start, so that they are ready to begin receiving notifications right away.
Is there an easy way to do this in iOS 5?
Why do the view changes straight away?
I would store an indicator of the changes needed when the users answers the questions on the first view and then apply the changes on -viewDidLoad of each view that needs to be changed.
Instead of trying to load the views into memory, I'd suggest you initialize these views with the options that the user set on the first view. What I usually do in such situations, when I have a global parameters that are used in many places, I create a utility class to keep the data, make it a singleton, then access the shared instance in the viewDidLoad in the views that use the data during initialization.

UINavigationItem does not show activity indicator

My application has a UITabBarController. When the first view loads I start a new thread from its
- (void)viewDidAppear:(BOOL)animated;
method, which runs a task. When the task starts, it calls a delegate method from the first view and adds an activity indicator to the UINavigationItem. When it ends, it calls another delegate method from the first view, and hides activity indicator.
The problem is that the activity indicator is not shown, unless I go to another view and then back.
Without seeing your code, it's hard to guess what is happening. In the past, UIKit methods have not been thread-safe and were required to be called on the main thread. It seems like a long shot in this scenario, but have you ruled this out?