Does setNeedsDisplay:NO have any use at all? - objective-c

In Cocoa, when we want to redraw a view, we would send the view a setNeedsDisplay: message telling the view to redraw itself with a parameter of YES. I was wondering if there are any circumstances where you would want to send a view setNeedsDisplay:NO, such as multithreading environments, and if sending a view a setNeedsDisplay:YES, then setting it again immediately after with setNeedsDisplay:NO would make the view redraw itself. If there are no reasons to call setNeedsDisplay:NO, then why create such a tedious method, where they could instead implement something like [view redrawView]

setNeedsDisplay:NO may be used in case you want to discard previously called setNeedsDisplay:YES. E.g. sometimes it is easier to mark all subviews as needing display and then run an algorithm to unmark some of them.

As you perhaps know, the display update is automatic (if necessary) at each pass through the normal event loop. You call setNeedsDisplay: in order to force a display update in between if it is necessary.
From the documentation of NSView:
Discussion
Whenever the data or state used for drawing a view object changes, the view should be sent a setNeedsDisplay: message. NSView objects marked as needing display are automatically redisplayed on each pass through the application’s event loop. (View objects that need to redisplay before the event loop comes around can of course immediately be sent the appropriate display... method.)
The boolean parameter to this function simply specifies if the entire bounds of the view in question is affected or not, not if some property "needsDisplay" is set to true or false. Thus, setNeedsDisplay: does indeed work pretty much like a "redrawView", only with the additional parameter.
Edit
The above was inspired from the same documentation:
flag
If YES, marks the receiver’s entire bounds as needing display; if NO, marks it as not needing display.

Related

Is -[NSView setsNeedsDisplay:] a synchronous call?

I have an NSView in a Mac OS X application that draws itself. When I am ready for this to happen I request it with the call:
[self.imageRenderedView setNeedsDisplay:YES];
My question is, does this call block? That is, is it synchronous, and can I assume the drawing has happened by the time the subsequent statement is executed? This assumption seems to work for me, but I feel a bit insecure about it.
The setNeedsDisplay: call only marks the view as needing display by setting
a flag in the view object.
Therefore it returns very quickly, but the drawing has not yet happened when the method returns.
From the documentation:
Whenever the data or state used for drawing a view object changes, the
view should be sent a setNeedsDisplay: message. NSView objects marked
as needing display are automatically redisplayed on each pass through
the application’s event loop. (View objects that need to redisplay
before the event loop comes around can of course immediately be sent
the appropriate display... method.)

Using NSManagedObjectContextObjectsDidChangeNotification

In many of my UIViewControllers, I update certain controls based on the state of my data. For example, I might have an edit button on a UITableViewController that should only be enabled when there is one or more items. Or perhaps I want to limit the number of items that can be added, and disable the 'add' button otherwise.
Every time I add or delete an item (or take any other action that can add/remove items), I have to remember to update any controls that might need enabling/disabling. This is trivial for the most part, but doesn't feel comfortable - there is a lot of repetition, and I have to remember to add the calls to updateControlEnabled (or whatever) whenever I add new functionality that might affect the data.
And then I noticed NSManagedObjectContextObjectsDidChangeNotification. Reading the docs, it looks like I can receive a notification whenever something changes in my managed object context. This seems ideal, but I have a few questions:
Is this an appropriate use of
NSManagedObjectContextObjectsDidChangeNotification?
Should I anticipate any performance impact if a controller
subscribes to these and parses each one to see if it needs to update
the UI? I will be checking the userInfo for every change, instead of
only those that I know I will care about.
Where should I subscribe to the notifications? My UIViewController has a
reference to the context, which helps, but I don't know where to
subscribe (loadView? viewDidLoad? init?) such that the view
controller will always have one and only one subscription.
The view controller will continue to receive and process notifications
when it's offscreen - enabling and disabling controls as the
data model is affected from elsewhere. Is this ok?
I guess I'm mostly just wondering if anyone else uses this approach and if so, what their experience is.
Q) Is this an appropriate use of NSManagedObjectContextObjectsDidChangeNotification?
A) Yes - I used it on OSX for a similar purpose.
Q) Should I anticipate any performance impact if a controller subscribes to these and parses each one to see if it needs to update the UI? I will be checking the userInfo for every change, instead of only those that I know I will care about.
A) NO - it will normally be a very small set of objects - ones that were directly changed.
Q) Where should I subscribe to the notifications? My UIViewController has a reference to the context, which helps, but I don't know where to subscribe (loadView? viewDidLoad? init?) such that the view controller will always have one and only one subscription.
A) Well, you cannot affect the UI til the view shows - so probably viewDidLoad or viewWillAppear. The problem with the later is you may get it a few times depending on push/pops, so maybe I'd do it in viewDidLoad.
Q) The view controller will continue to receive and process notifications when it's offscreen - enabling and disabling controls as the data model is affected from elsewhere. Is this ok?
A) Sure - when the view reappears all the elements will be setup correctly.
What you want to do is a classical use of that notification. Just check the thread it comes in on - if its not the mainThread then you want to make all your changes in a block posted to the mainThread.

Is setNeedsDisplay *always* repainting?

I wrote a little custom-view-application using cocoa. And later (yes, i know it's bad) I just asked myself: Would this work for cocoa touch as well? Of course id did not work instantly, I had to change the class names and so on. Well, i refreshed the View, whenever it was needed, using a NSTimer and the setNeedsDisplay: method. Worked pretty well under cocoa, but absolutely not under cocoa touch.
I can't explain it to myself an I actually don't know what lines of code could help someone to solve the problem. Maybe here is the Timer:
[self setMyTimer: [NSTimer scheduledTimerWithTimeInterval:0.03 target:self selector:#selector(myTarget:) userInfo:nil repeats:YES]];
And it's target:
- (void) myTarget:(NSTimer *)timer {
[self setNeedsDisplay];
}
The timer is invoked every 30 ms, I checked that with an NSLog.
In the drawRect: method I did actually just draw some shapes and did nothing else. Just in case it would be necessary to call some kind of clearRect: method. As I said, under cocoa it worked.
I would first verify whether drawRect: is running by using a breakpoint or log statement.
Then, make sure that your view is actually on the screen. What is the value of [self superview]? You should also do something like self.backgroundColor = [UIColor redColor]; so that you can see where your view is.
Just because you're marking the view dirty every 30ms doesn't mean it will draw every 30ms. It generally should (that's about 30fps), but there isn't a guarantee. drawRect: shouldn't rely on how often it's called. From your question, I assume you mean that it's never drawing, rather than just not drawing as often as expected.
Here's the discussion about setNeedsDisplay (note the LACK of arguments) from the documentation of UIView:
You can use this method to notify the system that your view’s contents
need to be redrawn. This method makes a note of the request and
returns control back to your code immediately. The view is not
actually redrawn until the next drawing cycle, at which point all
invalidated views are updated.
You should use this method to request that a view be redrawn only when
the content or appearance of the view change. If you simply change the
geometry of the view, the view is typically not redrawn. Instead, its
existing content is adjusted based on the value in the view’s
contentMode property. Redisplaying the existing content improves
performance by avoiding the need to redraw content that has not
changed.
In contrast, here's the discussion about setNeedsDisplay: (note the argument) from the documentation of NSView:
Whenever the data or state used for drawing a view object changes, the
view should be sent a setNeedsDisplay: message. NSView objects marked
as needing display are automatically redisplayed on each pass through
the application’s event loop. (View objects that need to redisplay
before the event loop comes around can of course immediately be sent
the appropriate display... method.)

Update screen in Cocoa/Objective C following button press

Newbie Objective C/Cocoa question: I have an application with some data entry fields and a "do it" button. When the button is pressed, some computation takes place and output data is displayed in a table view and some text fields in the same window. What I'd like is that when the button is pressed that the text fields and the table view are both cleared while the computation takes place.
I've tried making the appropriate calls as the first few statements of the action routine for the button press, but that doesn't work. I would imagine that the runtimes don't get called to do the screen update until after my action routine is finished.
Is there a simple way to do what I want to do? Thanks.
You imagine correctly.
The usual way to do this sort of thing is to use NSObject's performSelectorInBackground:withObject: to start the heavy calculation in the background. Then once the background code finishes doing its work, use performSelectorOnMainThread:withObject:waitUntilDone: to call another selector on the main thread to update the UI (remember, UI calls may only be done from the main thread).
You're correct about the screen updates not taking place until after your routine finishes. Most drawing to the screen is queued to improve performance.
When you change the value in an NSTextField, it knows to call [self setNeedsDisplay:YES] in order to queue its need for redrawing. If you want to force it to display, you can call [textField display]. (Note that calling [textField setNeedsDisplay:YES] will not cause immediate display). Things get a bit more difficult with an NSTableView, as this -display method is unlikely to work for it.
While you could create a secondary thread to do your processing, that would create a lot of complexity that may not be worth it. You might consider using -performSelector:withObject:afterDelay: to begin your processing routine rather than calling it directly.
- (IBAction)buttonClicked:(id)sender {
[textField setStringValue:#""];
[tableView reloadData];
// instead of doing the following:
// [self processData:nil];
// do
[self performSelector:#selector(processData:) withObject:nil afterDelay:0.0];
}
- (void)processData:(id)sender {
// process the data
[textField setStringValue:#"the results"];
[tableView reloadData];
}
Using -performSelector:withObject:afterDelay: is different than calling the method directly, as it causes the method to be called not immediately, but scheduled to be called "ASAP". In many cases, your app will be able to squeeze in the updates to the UI before it can get to performing that computation method. If testing reveals this to be the case, then you can avoid having to go to the trouble of creating a secondary thread to do the processing.
If you want to force updating screen then call setNeedsDisplay from your UIView.
I would imagine that the runtimes
don't get called to do the screen
update until after my action routine
is finished.
Bingo. Your button's action method is called on the main thread, which is the same thread that is responsible for updating the user-interface. So the interface will not update until after your action method returns.
To get around this, you can split your action method into two parts. The first part makes the calls to clear your previous view and set whatever new state you want to use for rendering. The second part does the new calculations, and is moved to its own method. Then, at the end of the first part, add something roughly like:
[self performSelectorInBackground:#selector(myActionSecondPart) withObject:nil];
...to run the computation part in the background. Then your UI will update while the computation runs.

NSTableView -setDataSource not working when triggered by FSEvents

So here's what I've got:
An NSTableView with an NSMutableArray data source
FSEvents monitoring a folder that contains the file that contains the data for the table view (Using SCEvents for Objective-C abstraction goodness)
The FSEvents triggers the same function that a reload button in the UI does. This function refreshes the table view with a new data source based on the contents of said file via setDataSource:.
And here's what happens:
If I make a change to the file, the FSEvent gets triggered and the refresh method gets called.
The array that the table view should be accepting does indeed include the changes that triggered the FSEvent.
setDataSource: gets sent to the NSTableView with the correct data source.
The changes do not appear in the table view!
But then:
If I hit the refresh button, which triggers the exact same method as the FSEvent, the table view gets updated with the new data.
I also tried replacing the FSEvent with an NSNotification (NSApplicationDidBecomeActiveNotification), but the same thing happens.
Anyone have any idea why this is happening?
Edit: For clarification, the jist of my question is this: Why does my NSTableView reload as it should when triggered by a button press, but not when triggered by an FSEvent or an NSNotification?
Edit: Thanks to diciu, I've figured out that in fact all of my UI references point to 0x0 when triggered by the event, but then have valid addresses when triggered by the button click. These objects are all declared in IB, so there's no instantiation or allocation for them going on in my code. So my question is now: what can I do to stop these pointers from pointing to nil?
We call reloadData on NSTableView when we have new data to add/remove to the table.
This might help, to force the NSTableView to redraw.
I'm not really sure if this is what your asking though. The wording of your question is kind of confusing, you state a series of events, but never a true question.
sounds like when you register for the event/notification, you're passing in a different instance of your controller class.
Have you tried calling your method from your FSEvent on a second pass of the run-loop?
[myObject performSelector:#selector(reloadAction:) withObject:nil afterDelay:0.0];
You're setting an NSArray directly as the data source of the table view?
That's not how NSTableView works. The data source must be an object that conforms to NSTableDataSource. NSArray doesn't. You write the data source yourself; it will probably be the same object that you currently have calling setDataSource:.
The other way would be to use Bindings.
Could it be that reference to table view within the scope of your refresh method is not valid?
I.e. are you sure you're not calling [nil reloadData] which does not yield any errors?
Your reference to your table view might be nil in the refresh code if you're set it before awakeFromNib or in some other circumstances.