Is setNeedsDisplay *always* repainting? - objective-c

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

Related

Xcode (Obj-C) Updating a UIView (NSRect) after the window is already visible to the user

I am fairly new to creating Xcode projects using Objective-C and i'm trying to make a simple 2d graphics program to show me how well my graphing code works before I implement it elsewhere.
I have gotten the drawing of everything right but the problem comes when I want to clear the strokes and stroke new lines. From what I see from when I run the program the view will only display what I have executed once it hits that #end at the end of the implementation. The issue with this is that by the time it hits the #end the code has already been run. and I can't figure out if I need to recall the class in a loop to update the view each time (or where or how to do this, perhaps something in main.m?) or if I need to call a function to update the view before it exits the implementation because right now all the lines are just overwriting each other before the user can see anything.
Here is the interface in my header file (connected to a UIView):
#interface GraphView : NSView
- (void)drawRect:(NSRect)dirtyRect;
#end
Implementation file:
Here is how I am creating my rectangle:
- (void)drawRect:(NSRect)dirtyRect {
[self clearGrid:dirtyRect];
[self drawLines];
}
- (void)clearGrid:(NSRect)theRect {
//Wiping the slate clean
[super drawRect:theRect];
[self.window setBackgroundColor:[NSColor whiteColor]];
...
Here is what I am using to draw my lines:
NSBezierPath* eqLine = [NSBezierPath bezierPath];
[[NSColor greenColor] setStroke];
[eqLine setLineWidth:5.0];
[eqLine moveToPoint:NSMakePoint([self convertToPixels:previousX], [self convertToPixels:previousY])];
[eqLine lineToPoint:NSMakePoint([self convertToPixels:finalX], [self convertToPixels:finalY])];
[eqLine stroke];
I have been searching for the past few days now on how I could solve this but so far it hasn't turned up anything, perhaps i'm just not searching for the right thing. Any information is helpful even if it's just a point to a resource that I can look at. Let me know if any additional information is needed. Thanks!
It's clear that you're a noobie to this, which is not a crime. So there's a lot to address here. Let's take things one at a time.
To answer your basic question, the -drawRect: method is called whenever a view needs to draw its graphic representation, whatever that is. As long as nothing changes that would alter its graphical representation, the -drawRect: method will only be received once.
To inform Cocoa (or CocoaTouch) that the graphic representation has changed, you invalidate all, or a portion of, your view. This can be accomplished in many ways, but the simplest is by setting the needsDisplay property to YES. Once you do that, Cocoa will (at some point in the future) call the -drawRect: method again to modify the graphic representation of your view. Wash, rinse, repeat.
So here's the basic flow:
You create your class and add it to a view. Cocoa draws your view the first time when it appears by sending your class a -drawRect: message.
If you want your view to change, you invalidate a portion of the view (i.e. view.needsDisplay = YES). Then you just sit back and wait. Very soon, Cocoa will send -drawRect: again, your class draws the new and improved image, and it appears on the screen.
Many changes will cause your view to be invalidated, and subsequently redrawn, automatically—say, if it's resized. But unless you make a change that Cocoa knows will require your view to redraw itself, you'll have to invalidate the view yourself.
Now for the nit-picking, which I hope you understand is all an effort to help you understand what's going on and improve your code...
Your subject line says UIView but your code example subclasses NSView so I'm actually not sure if you're writing a macOS or an iOS app. It doesn't matter too much, because at this level the differences are minimal.
Your -drawRect: calls [self clearGrid:..., when then calls [super drawRect:... Don't do this. As a rule, never use super except from within the overloaded method of the same name. In other words, -drawRect: can use [super drawRect:, but no other methods should. It's not "illegal", but it will save you grief in the long run.
Your -clearGrid: method sets the backgroundColor of the window. Don't do this. The window's background color is a property, and your -drawRect: method should only be drawing the graphical representation of your view—nothing more, nothing less.
You're calling [super drawRect: from within a direct subclass of NSView (or UIView). While that's OK, it's unnecessary. Both of these base classes clearly document that their -drawRect: method does nothing, so there's nothing to be gained by calling it. Again, it won't cause any harm, it's just pointless. When your -drawRect: method begins execution, a graphics context has already been set up, cleared, and is ready to draw into. Just start drawing.
#end is not a statement. It does not get "executed". It's just a word that tell the compiler that the source code for your class has come to an end. The stuff that gets executed are the methods, like -drawRect:.
In your #interface section you declared a -drawRect: method. This is superfluous, because the -drawRect: method is already declared in the superclass.

NSView setNeedsDisplay causing a performance hit even when draw rect is commented out

I have a Cocoa app that draws a lot data to the main screen(31000 samples by about 315 channels) so we are being very studious about profiling and getting everything as efficient as possible. I have a window controller that when opened updates it's view every 2 seconds based on the data. I am using an NSTimer and specifying the view update method.
The problem I am having is every time the timer fires the method, the main display hiccups slightly. I thought it would just be a matter of optimizing the drawRect method in the view subclass, but when I could not find any specific area in the draw rect method where the performance was bad, I decided to try commenting out the contents of the drawRect method.
results:
If I comment out the contents of the drawRect method, I will still get a hiccup.
If I comment out the call to [view setNeedsDisply: YES] in the calling method, it resolves the hiccup.
What Ive Tried:
1) I modified the method calls so that when the timer fired I was using performSelectorOnMainThread to call the view
2) I then tried to use the main dispatch queue with async.
neither of these things worked.
There is some kind of lag happening here even when there is no drawing work to do.
Any help is appreciated.

How do I notify incoming scene when presentScene:transition: is complete?

Let's say I'm transitioning from an old SKScene of an SKView to a new one:
//In some controller somewhere...
-(void)swapScenes{
MySceneSubclass *newScene = [[MySceneSubclass alloc] initWithSize:CGSizeMake(1024, 768)];
SKTransition *transition = [SKTransition crossFadeWithDuration:REALLY_LONG_DURATION];
[[self mySKView] presentScene:newScene transition:transition];
}
Let's additionally say I want my new scene to perform some action or animation once the transition is completed, but not before. What would I use to trigger that?
At first I thought I'd be able to override didMoveToView: to do this, but it turns out this is called at the very begining of the transition (in hindsight, this makes sense. In a crossfade, the incoming scene is composited at the very beginning of the animation, even if its opacity is very low).
Next, as a hail mary, I tried inserting a call to the new scene right after presentScene:
-(void)swapScenes{
MySceneSubclass *newScene = [[MySceneSubclass alloc] initWithSize:CGSizeMake(1024, 768)];
SKTransition *transition = [SKTransition crossFadeWithDuration:REALLY_LONG_DURATION];
[[self mySKView] presentScene:newScene transition:transition];
[newScene doSomethingAfterTransition]; //<----
}
But presentScene: predictably returned immediately causing this method to be called long before the transition had completed.
As a last resort, I'm considering something like:
[newScene performSelector:#selector(doSomethingAfterTransition) afterDelay:REALLY_LONG_DURATION];
But I'd really like to avoid that if at all possible. It seems like there ought to be an delegate action or notification or something that knows when the transition is over, right?
The answer to this was staring me in the face. As I mentioned above, in a transition both scenes need to be present throughout the animation. Thus the incoming scene's didMoveToView: is called immediately at the beginning of the transition instead of at the end as I expected.
Of course, by this same logic, the outgoing scene's willMoveFromView: won't get called until the end of the transition. Which is what I was looking for in the first place.
So, you can override -willMoveFromView: of the outgoing scene (or, more likely, some shared superclass) to send a notification or call a delegate or whatever you like when transition completes. In my case, I have it call a block so I can keep everything local to my -swapScenes method, but YMMV.
Perform selector after delay with the same delay as the transition is perfectly reasonable.
This is a commonly desired need and (ideally) we shouldn't be programming around the tools. The two workaround provided by #jlemmons and #LearnCocos2D are both functional, but each have their fallbacks.
I would highly suggest going to the Apple Bug Reporter and requesting the addition of
- (void)[SKScene didFinishTransitionToView:(SKView *)view]
The more people request, the sooner it may appear.

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

Calling -setNeedsDisplay:YES from within -drawRect?

I am customizing my drawRect: method, which serves to draw a NSImage if it has been "loaded" (loading taking a few seconds worth of time because I'm grabbing it from a WebView), and putting off drawing the image till later if the image has not yet been loaded.
- (void)drawRect:(NSRect)dirtyRect
{
NSImage *imageToDraw = [self cachedImage];
if (imageToDraw != nil) {
[imageToDraw drawInRect:dirtyRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:YES hints:nil];
} else {
//I need help here
[self setNeedsDisplay:YES];
}
}
My question is how to do the latter. [self cachedImage] returns nil if the image is unavailable, but anytime within the next few seconds it may become available and at that time I want to draw it because the custom view is already on screen.
My initial instinct was to try calling [self setNeedsDisplay:YES]; if the image wasn't available, in hopes that it would tell Cocoa to call drawRect again the next time around (and again and again and again until the image is drawn), but that doesn't work.
Any pointers as to where I can go from here?
EDIT:
I am very much aware of the delegate methods for WebView that fire when the loadRequest has been completely processed. Using these, however, will be very difficult due to the structure of the rest of the application, but I think I will try to somehow use them now given the current answers. (also note that my drawRect: method is relatively light weight, there being nothing except the code I already have above.)
I currently have about 10+ custom views each with custom data asking the same WebView to generate images for each of them. At the same time, I am grabbing the image from an NSCache (using an identifier corresponding to each custom view) and creating it if it doesn't exist or needs to be updated, and returning nil if it is not yet available. Hence, it's not as easy as calling [view setNeedsDisplay:YES] from - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame or another method.
My initial instinct was to try calling [self setNeedsDisplay:YES]; if the image wasn't available, in hopes that it would tell Cocoa to call drawRect again the next time around (and again and again and again until the image is drawn)
This would be incredibly inefficient, even if it worked.
anytime within the next few seconds it may become available and at that time I want to draw it
So, when that happens, call [view setNeedsDisplay:YES].
If you have no means of directly determining when the image becomes available, you'll have to poll. Set up a repeating NSTimer with an interval of something reasonable -- say 0.25 second or so. (This is also pretty inefficient, but at least it's running only 4 times per second instead of 60 or worse. It's a tradeoff between two factors: how much CPU and battery power you want to use, and how long the delay is between the time the image becomes available and the time you show it.)
my drawRect: method is relatively light weight, there being nothing except the code I already have above.
Even if you do nothing at all in -drawRect:, Cocoa still needs to do a lot of work behind the scenes -- it needs to manage dirty rects, clear the appropriate area of the window's backing store, flush it to the screen, etc. None of that is free.
Well, usually there is some delegate method that is called, when a download of something finishes. You should implement that method and call setNeedsDisplay:YES there.
The documentation for webkit:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DisplayWebContent/Tasks/ResourceLoading.html#//apple_ref/doc/uid/20002028-CJBEHAAG
You have to implement the following method in your webview delegate:
- webView:resource:didFinishLoadingFromDataSource:
There you can call [view setNeedsDisplay:Yes]