Synchronize drawRect for ViewController - objective-c

I have a view controller class that parses a clip's frames and renders it into an NSOpenGLView. Problem is, the frames being rendered are choppy as it seems the view controller is telling the view to draw faster than it could handle. See code snippet below for reference:
// Snippet code from view controller
- (void) tellViewToDrawWithFrameAtTimeInterval:(float)timeVal
{
_myGlobalTimer = [NSTimer scheduledTimerWithInterval:timeVal
target:self
selector:#selector(triggerRenderAtTime)
userInfo:nil
repeats:YES];
}
- (void) triggerRenderAtTime
{
[_myView setPixelData:[_myPixelBuffer objectAtIndex:_frameIndex];
[_myView setNeedsDisplay:YES];
_frameIndex++;
}
// The NSTimer object is eventually killed elsewhere in the class' implementation
I even tried to set the timer to call at intervals of 3, 5, 10 seconds just to ensure the view has time to process everything and render the frame. However, the results observed was still the same - the view is still skipping frames that it should be drawing. I'm currently looking at CVDisplayLink, but I'm very unfamiliar as to what they do yet, and based on what I've read so far, it seems to be a complex solution on an otherwise simple problem (I think...). Can anyone shed some light to this? Thanks!

Okay, I figured it out, and I had to eliminate drawRect completely and use CVDisplayLink for my drawing. Apple's guide was perfect for this:
Technical Q&A QA1385 - Driving OpenGL Rendering Loops

Related

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.

Calling a complicated method from another method in Objective C

I've seen many cases here where someone asks how to call one extremely simply-named method from another method, and gets a simple answer.
However, I have a CGRect method
- (void)drawRect:(CGRect)rect { /* code that draws circles */}
which successfully draws nmax circles for me.
I also have a button method that successfully updates an "nmax" displayed on the screen.
- (IBAction)changeIntValue:(id)sender {nmax=nmax+100;}
Only problem is that I want it to redraw the screen for me as well. I cannot for the life of me figure out, even after Ring some FM's, the syntax to "re-call" this rect down again in my changeIntValue method.
Any help greatly appreciated.
While I'm typing, if anyone has a reference with exhaustive info on syntax, that would be helpful. References at developer.apple.com and most tutorials veer off into giving specific examples likeThisExample. I hand-wrote some notes off a youtube lecture which gave syntax, but lost the reference.
You can cause an item to redraw using
[view setNeedsDisplay];
where view is obviously your view.
This causes the view to be redrawn in the UI thread, and your view drawing code should then call your drawRect methods as appropriate.

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]

performSelectorInBackground and MapKit

I am working with MapKit and annotations in iOS. Right before the annotations are added (which can take a while), I would like to display a small "Loading..." UIView. If I do this without threading, the loading view lags so much that it barely appears before it's dismissed. If I use performSelectorInBackground to load the annotations, it will work every few tries and the rest of the UIView will appear but no annotations, even though mapView:didAddAnnotationViews: is called. Can anyone think of why this would be behaving so unpredictably?
This is how I'm calling it, if it helps:
[self performSelectorInBackground:#selector(refreshAnnos) withObject:nil];
You can load the annotations on the background, but you should always ADD them on the main thread.
That is why
mapView:didAddAnnotationViews:
is called, but does not propagate to the UI. It would be the same as calling the UITableViewDelegate methods in the background.
You could possibly try the following design pattern using GCD
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//Load the annotations on the background queue;
dispatch_async(dispatch_get_main_queue(), ^{
//add the annotations to the mapView;
});
});
What do you do in refreshAnnos?
If you're adding the annotations to the mapview there, it will not work (because of the background thread). You need to update the mapview inside of the main thread.
Best,
Christian

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