How to implement drawLayer:InContext in the implementation file of a UIView? - ios

I have a UIView in which I draw things in the drawRect method. I would like to overlay a CALayer over the UIView CALayer (self.layer) that would draw a subset of the things I draw in the drawRect. Basically, I draw a lot of circles in drawRect, and I would like to highlight some them on the overlay. I get the coordinates of the circles from a model object that is a property of the UIView.
My first attempt was to add a sublayer s to the UIView layer, set its delegate to the UIView and call its setNeedsDisplay method when needed, but drawLayer:InContext is called by both layers (s and self.layer). This doesn't work:
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
if (layer == s ) {
// draw stuffs for s
}
else if (layer==self.layer) {
// do nothing
}
}
results in a black UIView. I would like to draw s from a place where I have access to my data model, and other variables useful for the drawing (width of lines..). I have kinda solved the problem by moving the drawing of s to a separate object, but this forces me to also set a pointer of the model in this object, and also copy other parameters.
So my question is: in a UIView drawing its content using drawRect, how to add a sublayer which delegate is the same UIView ?

You need to subclass UIView and in that subclass override + (Class)layerClass with your own CALayer subclass. Then that UIView will use that subclass when it creates its backing layer. Your CALayer subclass would then have an override for drawLayer.
You can put this CALayer subclass (interface and implementation) in your UIView subclass.

Related

make custom CALayer subclass automatically redraw itself after being added to a super layer

I am subclassing CALayer and doing custom drawing in drawInContext: method.
The problem is I need to manually call setNeedsDisplay: after I add an instance of my subclass to a superlayer, something like:
MyCustomLayer *instance = [MyCustomLayer layer];
//do some configurations
[self.view.layer addSublayer:instance];
[layer setNeedsDisplay]; //required
or the drawing will not happen.
But when I use buit-in CALayer subclasses (CATextLayer, CAShapeLayer etc.), the call to setNeedsDisplay: is not needed after adding them to a super layer. Can I make my subclass behaves like those classes?
You can call displayIfNeeded on yourself within the init method, however better is to subclass needsDisplayForKey: and return YES for the #"transform" key, which would redraw when the bounds changes (i.e. when first added to the view).

Slow drawInRect with same image

I have a custom view inheriting from UIView.
In this view, there is an image which is always the same and I only want to draw on top of that.
However, in order for the image to be drawn, I have to call drawInRect every time the draw cycle runs.
- (void)drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
[image drawInRect:self.bounds];
// drawing....
}
Is there a way to show the image and not to call this method every time drawRect is invoked?
Thank you.
Put the image in a UIImageView underneath your custom UIView instead of drawing it in drawRect:. Make your custom view's background color clearColor so the image shows through where drawRect: doesn't draw anything.
Don't try to make the image view a subview of your custom view. A subview always draws on top of its superview's content. You could make your custom view a subview of the image view, or you could make them siblings.
In the .h file of the custom class do this:
- (void) setup:
Then in the .m do this:
-(void) setup {
//drawing stuff
}
Then in the view did load of whatever the view controller that adds it as a subview is
[myCustomView setup];

CALayer not implementing custom drawLayer:inContext: method

I saw this solution while researching for CALayers. I was looking for a way to implement custom drawings inside a UIView with multiple sublayers. I named my sublayers like this:
layer1.name = #"Back";
layer2.name = #"Middle";
layer3.name = #"Front";
And I created custom methods to be implemented by this layers
-(void)drawBackLayer:(CALayer *)layer inContext:(CGContextRef)ctx
-(void)drawMiddleLayer:(CALayer *)layer inContext:(CGContextRef)ctx
-(void)drawFrontLayer:(CALayer *)layer inContext:(CGContextRef)ctx
The problem is these methods are not implemented, instead the drawLayer:inContext:, which is being used by the view's root layer, is implemented four times. This means that the custom layers implement this method instead of the custom methods. Can anyone explain to me why?
NOTE: the solution I am referring in the link is the code provided by Dave Lee.
The layer used by the UIView must be a custom CALayer that implements those messages. The way you get a UIView subclass to use a particular layer is via the class method "+(Class)layerClass".
So:
- subclass a UIView (or subclass of it like UIImageView, etc)
- implement the layerClass method, and return [MyCALayer class];
Now that view will use YOUR CALayer subclass.

Animate a UIView's CoreGraphics/drawRect content

Is it possible to animate a UIView's CoreGraphics content?
Say I have a UIView subclass called MyView that implements the drawRect: method like so:
- (void) drawRect: (CGRect) rect {
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(c, someColor);
CGContextSetLineWidth(c, someWidth);
CGContextMoveToPoint(c, leftOfMyUIView, topOfMyUIView);
CGContextAddLineToPoint(c, leftOfMyUIView, bottomOfMyUIView);
CGContextStrokePath(c);
}
In other words - it draws a vertical line down the left hand side of my subclass of UIView. Is there an easy way to now animate this UIView so that the line moves from the left side to the right side of the view? I would like it to 'drift' from left to right.
To be clear - for reasons I will not go into I cannot move/animate the instance of MyView. The best solution I can think of using is to add a new UIView subclass. Something like LineView extends UIView and then make LineView the exact dimensions of the line I want to draw, fill it using its own drawRect method and then add an instance of LineView to MyView as a subview. This would allow me to animate the position of the LineView object using an animation block, but seems overly complicated to achieve something so simple.
Based upon your comment that there will be hundreds of LineView instances, it may be best to use CALayer subclasses as opposed to UIView subclasses. UIView objects give you event handling, and on iOS each UIView owns a CALayer to handle its rendering. If you don't want/need event tracking for the individual lines, all those UIView instances are probably needless overhead. You can access the parent UIView's layer, and add your line sublayers as needed.
If I understand your problem correctly, there should be no need for CoreGraphics. You can set the line layers' backgroundColor to handle the fill. Or if the lines will not be straight, you can use CAShapeLayer to render the paths of the lines.

Blacking out areas of a UIImageView or showing part of the view

Is there a way to black out a region (defined by a frame) of a UIImageView without creating an overlaying UIView instance (with this frame) and blacking that out?
Is there similarly a way to reveal part of a UIImageView without using UIView instances to black out the rest of the image?
I am going to cheat a bit here and suggest that you use layer as they are light weight.
CALayer *blackLayer = [CALayer layer];
blackLayer.backgroundColor = [UIColor blackColor];
blackLayer.frame = imageView.bounds;
[imageView.layer addSublayer:blackLayer];
For the second part you can consider using a grid of layers (black). When user touches the image view, you can pick the layers from the area he has touched and remove them from the super layer i.e. the image view's root layer.
you can subclass the UIIMageView and draw as per your needs in the drawRectMethod.
You can create a subclass of UIIMageView and have your definition of drawRectMethod over there.
You can also invoke the drawRectMethod at your will by calling setNeedsLayout method on the view .( setNeedsDisplay - causes drawRect to be called - . Only contentMode = UIViewContentModeRedraw will cause setNeedsDisplay to be called when a view's bounds changes).
UPDATE:
http://developer.apple.com/library/ios/#documentation/CoreGraphics/Reference/CoreGraphics_Framework/_index.html

Resources