Calling -setNeedsDisplay:YES from within -drawRect? - objective-c

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]

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.

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

CABasicAnimation and custom types

I'm not very familiar with CoreAnimation, so I hope I've just missed something pretty simple. I want to animate a custom property (NSGradient) of a NSView in a simple manner, with [[view animator] setGradient:gradient];. I defined + (id)defaultAnimationForKey:(NSString *)key and returned a simple CABasicAnimation, however, no animation is executed. Since this works for simpler types and NSColor, I guess CABasicAnimation doesn't work with gradients. Fine, but in this particular case gradients are trivial (two stops, always), so I can easily write an interpolation functions. The question: how can I define a custom interpolation? I googled around regarding delegates on view, layer and animations, subclassing animation class etc., but I wasn't able to figure the things out. Thanks!
I thought I remembered passing by some Apple documentation when I was learning how to use Core Animation that showed how to set up animations that couldn't be handled by properticode describedes that are supplied with defined animations. Along the way I stumbled across some sample code from Apple that is described as:
A single gradient layer is displayed and continuously animated using new random colors.
That may be the answer to the specific task you already handled another way. I found it in the Documentation and API Reference within Xcode and the name of the sample code is simply Gradients. (Note that there is an original version 1.0 and an updated version 1.1 that was redone this year in April and so should be easier to use with current tools.
But, the larger question of creating a custom animation that can't be automated by Core Animation itself is to follow the example from Apple's Animation Programming Guide for Cocoa in the section Using an NSAnimation Object. It's described under the topic Subclassing NSAnimation and the recommended method is shown under the heading Smooth Animations. You override the setCurrentProgress: method so that each time it is called you first invoke Super so that NSAnimation updates the progress value, i.e., your custom animated property and then do any updating or drawing needed for the next frame of your animation. Here are the notes and example code provided by Apple in the referenced documentation:
As mentioned in “Setting and Handling Progress Marks,” you can attach a series of progress marks to an NSAnimation object and have the delegate implement the animation:didReachProgressMark: method to redraw an object at each progress mark. However, this is not the best way to animate an object. Unless you set a large number of progress marks (30 per second or more), the animation is probably going to appear jerky.
A better approach is to subclass NSAnimation and override the setCurrentProgress: method, as illustrated in Listing 4. The NSAnimation object invokes this method after each frame to change the progress value. By intercepting this message, you can perform any redrawing or updating you need for that frame. If you do override this method, be sure to invoke the implementation of super so that it can update the current progress.
Listing 4 Overriding the setCurrentProgress: method
- (void)setCurrentProgress:(NSAnimationProgress)progress
{
// Call super to update the progress value.
[super setCurrentProgress:progress];
// Update the window position.
NSRect theWinFrame = [[NSApp mainWindow] frame];
NSRect theScreenFrame = [[NSScreen mainScreen] visibleFrame];
theWinFrame.origin.x = progress *
(theScreenFrame.size.width - theWinFrame.size.width);
[[NSApp mainWindow] setFrame:theWinFrame display:YES animate:YES];
}
So basically you define a "progress value" (possibly composed of several values) that defines the state of your custom animation and write code that given the current "progress value" draws or changes what is drawn when the animation is at that particular state. Then you let NSAnimation run the animation using the normal methods of setting up an animation and it will execute your code to draw each frame of the animation at the appropriate time.
I hope that answers what you wanted to know. I doubt I could have found this easily by searching without having seen it before since I finally had to go to where I thought it might be and skim page by page through the entire topic to find it again!

Performance of self.view for NSCollectionViewItem

I'm looking at using an NSCollectionView for a photo library application, which will allow browsing of 10,000+ images. I'm not sure if NSCollectionView is the best thing for this, or if I should be looking at other methods, so feel free to throw your 2 cents in if you have an opinion.
Anyway, in the (custom) class holding the NSCollectionViewItem, when a specific bound value is updated, I call a method on the item's view.
Problem is, just accessing [self view] is taking an exorbitant amount of time. This was demonstrated by breaking the call to [self view] into another method and running instrumentation over it. The following method
- (NSBox*)getBox {
return (NSBox*)[self view];
}
takes about 100 times more processing time that any other single method. Yes, it's called many times, but if I change to return self, rather than self.view, the getBox method doesn't even appear in the processing list.
Any ideas on why this should be the case?
Why not use Image Kit's IKImageBrowserView, which is basically an iPhoto-like image browser?
There's the ImageKitDemo and ImageBrowser sample projects.
Regarding the performance issue with [self view], it might be that the collection view is being loaded lazily from the nib each time, and this call to [self view] is what's causing another instance to be instantiated from memory or from disk. While I haven't used NSCollectionView myself, my gut feeling is that there's probably going to be a greater overhead with numerous NSViews compared to IKImageBrowserView which uses the standard coupling of an NSView with an NSCell-like reusable drawing mechanism to help improve performance.
See Avoid the Overuse of Views:
"A good example of when to use custom
objects is a photo browser that
displays thumbnail images of hundreds
or even thousands of photos. Wrapping
each photo in an NSView instance would
both be prohibitively expensive and
inefficient. Instead, you would be
better off by creating a lightweight
class to manage one or more photos and
a custom view to manage that
lightweight class."
(In many places, a single NSView subclass will re-use an NSCell subclass to help implement the drawing of its contents. In this respect, the NSCell basically acts as a little "drawing machine").