Implementing custom CALayer Subclass to draw with Core Graphics commands - objective-c

I am implementing a UIview to contain a number of layers (about 9) to draw different elements of a graph in real time. I had previously implemented these as 9 different UIViews, and drew on them using the drawRect() function and it worked fine... but was very slow. From what I've been able to find online, it seems as if CALayers will be much faster. To make this change, I've subclassed CALayer, and only overriden the drawinContext: function. Here is the entirety of my CALayer.m file
#import "FFTLayer.h"
#implementation FFTLayer
#synthesize strokeColor,points,numPoints;
-(void)display{
[self drawInContext:UIGraphicsGetCurrentContext()];
NSLog(#"displaying!!");
[super display];
}
-(void)drawInContext:(CGContextRef)ctx
{
NSLog(#"drawing in context!");
CGContextSetStrokeColorWithColor(ctx, strokeColor);
CGContextStrokeLineSegments(ctx, points, numPoints*2);
}
#end
I have tried a bunch of different things. but so far, the only way that I've been able to get drawInContext: to be called is by calling it in the display() method, which seems wrong. Even when I do get drawInContext to be called, Xcode tells me that my drawing context is invalid. Here is the code that I'm using to try and tell FFTlayer to call itself.
context = UIGraphicsGetCurrentContext();
[self.layer addSublayer:fftLayer];
[fftLayer setPoints:fft_points];
[fftLayer drawInContext:context];
[fftLayer setNeedsDisplay];
[self performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone:NO];
Even after spending a few days reading about CALAyers online, I am still pretty confused about how to properly use them, and if I'm drawing this the most efficient/correct way. In case you can't tell from the "FFTLayer" class name, I'm an audio guy, not a graphics guy :)
Any help would be greatly appreciated!!
EDIT:
I set up all of my layers in the following way
//FFT Graph Layer
fftLayer = [[FFTLayer alloc] init];
fftLayer.backgroundColor = [UIColor clearColor].CGColor;
fftLayer.frame = self.layer.bounds;
[fftLayer setNumPoints:nP];
[fftLayer setStrokeColor:fft_strokeColor];

Did you import "FFTlayer.h" in your view subclass?
Try to add after layer init:
fftLayer.delegate = fftLayer;

Related

Cocoa Application does not redraw after overlapping another application

After I overlay an application over my previous application, I go back to previous application and encounter a few errors:
certain components have disappeared
only way to make the components visible is to resize the window
that seems to redraw the whole canvas.
Weird thing is that there are only a couple of components and drawn images that are missing
It doesn't always happen but only a couple of times
I haven't found a solid way to reproduce the problem.
Anybody have an Idea why this is happening?
I experienced exactly the same issue (view was updated correctly only after resizing), except that I've used OpenGL drawing in OSX game.
My problem was solved by adding this:
GLint vblSynch = 1;
[[self openGLContext] setValues:&vblSynch forParameter:NSOpenGLCPSwapInterval];
in my custom NSOpenGLView init method.
Then I've implemented:
- (void)drawRect:(NSRect)rect
{
[self destroyFramebuffer]; // glDeleteFramebuffers..
[self createFramebuffer]; // [super prepareOpenGl], glGenFrame(Render)Buffers, bind buffers, etc
[self drawView]; // [[self openGLContext] makeCurrentContext], make some drawing, [[self openGLContext] flushBuffer]..
}
like this.
After these changes, when window gets focus it redraws itself (without any resizing stuff :) ).
Hope this helps!

NSCell redrawing issues

I'm creating a NSCell subclass that draws some objects directly onto the view (using drawInRect:fromRect:operation:fraction:respectFlipped:hints:) and also draws an NSButton instance simply using NSView's addSubview: selector.
While objects drawn using the first method all draw correclty, I'm having problems with drawing the NSButton correctly. The issue is that my NSButton instances will draw in the right places, but multiple times over.
I've researched this on the internet for a while and some people suggested using a cache, but I'm not sure if this is efficient. (going an array containing buttons using a for loop will definately cause slow scrolling since I display a lot of data...)
How would you do this? Am I barking up the wrong tree?
This is the relevant code:
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
NSRect _controlRect = cellFrame;
float _Y = cellFrame.origin.y;
NSRect _accessoryRect = NSMakeRect(_controlRect.size.width - 70.0f, _Y + 9.0f, 50.0f, 23.0f);
_switch = [self _choiceSwitch];
[_switch setFrame:_accessoryRect];
[controlView addSubview:_switch];
}
Long story short: Friends don't let friends addSubview, while drawing.
This a fundamental, and not particularly well-explained aspect of managing control interfaces, but is important to come to grips with.
Let your controllers dictate the "order" of subviews, and you can sleep tight knowing that that button shouldn't get overtly mucked about (which is NOT the case if it's getting jostled around inside your custom drawing routines).
It's easy to get trapped in this alley, cause, like, hey, I added an NSImageView in my initWithFrame and everything seems to be okay… But it's just sort of not how you're supposed to do it, I guess… and when you start subclassing NSControl, etc. is when you start to realize why.
Updated: Here's a really good write up on designing custom controls with an equally as great sample project attached - which embodies the kind of code organization that can help avoid this type of issue. For example.. you'll notice in the controller class how he's keeping each button seperate, unique, and independent of other views' business…
for (int butts = 0; butts < 3; butts++) {
NSRect buttFrame = NSMakeRect(0, butts * 10, 69, 10);
ExampleButt *butt = [[ExampleButt alloc]initWithFrame:buttFrame];
[mainView addSubview:butt];
}
“Drawing” NSButton by adding its instance into the view hierarchy each time you draw the cell itself is definitely a bad idea. Instead, create an NSButtonCell and configure it up to your taste. Then, in your -[NSCell drawInteriorWithFrame:inView:] use a cell ivar to draw its appearance.
If you want to have a clickable NSButton instance in each cell of the table view, try to avoid a call to addSubview: when possible. Each time you do this, the control view may invalidate its layout and re-draw everything from scratch making some kind of a recursion in your case.

How to use a custom view correctly?

I have been trying to make a simple drawing program. Recently, I have figured out to draw shapes in a custom view for this purpose. My problem is that I have to draw everything at a single point in time. I don't know if that actually makes sense, but it seems to me that it calls the drawRect method only once, at that "once" is on startup.
Here is my code so far:
Header file.
NSBezierPath *thePath;
NSColor *theColor;
NSTimer *updateTimer;
NSPoint *mousePoint;
int x = 0;
int y = 0;
#interface test : NSView {
IBOutlet NSView *myView;
}
#property (readwrite) NSPoint mousePoint;
#end
Then, implementation in the .m file.
#implementation test
#synthesize mousePoint;
- (void) mouseDown:(NSEvent*)someEvent {
CGEventRef ourEvent = CGEventCreate(NULL);
mousePoint = CGEventGetLocation(ourEvent);
NSLog(#"Location: x= %f, y = %f", (float)mousePoint.x, (float)mousePoint.y);
thePath = [NSBezierPath bezierPathWithRect:NSMakeRect(mousePoint.x, mousePoint.y, 10, 10)];
theColor = [NSColor blackColor];
}
- (void) mouseDragged:(NSEvent *)someEvent {
mousePoint = [someEvent locationInWindow];
NSLog(#"Location: x= %f, y = %f", (float)mousePoint.x, (float)mousePoint.y);
x = mousePoint.x;
y = mousePoint.y;
[myView setNeedsDisplay:YES];
}
- (void) drawRect:(NSRect)rect; {
NSLog(#"oisudfghio");
thePath = [NSBezierPath bezierPathWithRect:NSMakeRect(x, y, 10, 10)];
theColor = [NSColor blackColor];
[theColor set];
[thePath fill];
}
#end
On startup, it draws a rectangle in the bottom left corner, like it should. The problem is, the drawRect method is only called on startup. It just won't fire no matter what I do.
EDIT: I have just updated the code. I hope it helps.
SECOND EDIT: I have really simplified the code. I hope this helps a bit more.
Short Answer:
When your view's state is changed such that it would draw differently, you need to invoke -[NSView setNeedsDisplay:]. That will cause your view's drawRect: method to be called in the near future. You should never call drawRect: yourself. That's a callback that's invoked on your behalf.
When events occur in your application that cause you to want to change your drawing, capture state about what happened into instance variables, invoke setNeedsDisplay: and then later when drawRect: is called do the new drawing.
Long Answer:
In Cocoa, window drawing is done with an pull/invalidation model. That means the window has an idea of whether or not it needs to draw, and when it thinks it needs to draw it draws once per event loop.
If you're not familiar with event loops you can read about them on Wikipedia
At the top level of the application you can imagine that Cocoa is doing this:
while (1) {
NSArray *events = [self waitForEvents];
[self doEvents:events];
}
Where events are things like the mouse moving, the keyboard being pressed, and timers going off.
NSView has a method -[NSView setNeedsDisplay:]. It takes a boolean parameter. When that method is invoked the window invalidates the drawing region for that view, and schedules an event for the future to do redrawing - but only if there isn't a preexisting redrawing event scheduled.
When the runloop spins next time, the views that were marked with setNeedsDisplay: are re-drawn. This means you can call setNeedsDisplay: several times in a row and drawing will be batched to one call of drawRect: in the future. This is important for performance reasons and means you can do things like change the frame of a view several times in one method but it will only be drawn once at the final location.
The code in your example has a couple of problems. The first is that all drawing code must be in the drawRect: method or a method called from drawRect:, so the drawing code you've placed in your other methods will have no effect at runtime. The second problem is that your code should never directly call drawRect:; instead, the framework will call it automatically (if necessary) once per event cycle.
Instead of hardcoding all the values, consider using instance variables for things you want to be able to change at runtime, for example, the drawing color and rectangle. Then in your mouseDragged: method, send the view (myView in your example) a setNeedsDisplay: message. If you pass YES as the argument, the drawRect: method will be called for you by the framework.

Optionally set navigationbar background image

I have the need to draw a background image or set a tint color on a navigation bar, but I also need the option to have the navigation bar appear as it normally would. I'm currently using a category to support If my app does not specify a background image, what can I do instead to ensure the drawRect method does it normally would do?
I.E. -
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
if(hasImage){
UIImage *img = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://myimageurl.com/img.jpg"]]];
[img drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}else{
??????
}
}
#end
I actually ended up doing something entirely different and I'm wondering why nobody hasn't discovered this before. One of the approaches I've seen in the course of my Googling on the subject was simply adding an image as a subview to the UINavigationBar. The only problem was this made the buttons in the bar not clickable. The fix was to disable user interaction on the image.
myUIImageView.userInteractionEnabled = NO;
[myNavController.navigationBar addSubview:myUIImageView];
[myNavController.navigationBar sendSubviewToBack:myUIImageView];
With that, everything looks/works great and I don't have to override the drawRect method with a category, swizzle methods or any of that funky stuff. Simple and clean.
Theoretically you could do this by subclassing UINavigationBar overriding only the drawRect: method, and then calling [super drawRect:rect] when you want to use the default behavior.
But I don't believe you can in practice because you don't instantiate the UINavigationBar directly.
Solving this is possible but nontrivial, since the category method "replaces" the original method rather than subclassing it from the runtime's standpoint. This is why just using, say, super, won't work.
You should check out this post on "supersequent" implementation: http://cocoawithlove.com/2008/03/supersequent-implementation.html
(That link and some other related ideas are in the answer to this question: Using Super in an Objective C Category? )

Background image for a window in Cocoa framework

I am looking for a perfect solution to set a background image for a window in a cocoa application. I haven't found a solution to this, I am new in objective c, so please anyone help me...
A window in Cocoa has a root-level view called the "content view". This is the view that contains all the others in a window. By default, it's just a plain, blank NSView. But you could easily create your own custom NSView subclass, override the drawRect: method to draw your background image, and use that for your custom view.
However, it might just be easier to use a plain old NSImageView. The advantage of this is that you can set, for example, autosizing behavior to keep the image pinned to one corner (try this with Installer.app by resizing the installer window). You would also be able to make it semi-opaque so that the background shows through a bit. (Again, I'm thinking of Installer.app; your app could be totally different)
Hope that gets you going in the right direction!
Michael Vannorsdel suggests sublassing NSView for the purpose, and I quote:
You'd really be better off making an
NSView subclass and having it draw
the image you want in drawRect:.
- (void)awakeFromNib
{
myImage = [[NSImage alloc] init....
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)rect
{
NSSize isize = [myImage size];
[myImage drawInRect:[self bounds] fromRect:NSMakeRect(0.0, 0.0,
isize.width, isize.height) operation: NSCompositeCopy fraction:1.0];
}
Read that whole thread on cocoabuilder, it's quite instructive.