CALayer not implementing custom drawLayer:inContext: method - ios

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.

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

Multiple CALayer instances created by a UIScrollView?

I'm working on a project where I'm using a CAShapeLayer to render a grid behind some content. The problem is that the OS creates multiple instances of this CAShapeLayer subclass, when I'm expecting one.
First, I subclassed CAShapeLayer, overriding init and layoutSublayers (the layer has a few sublayers that render additional content).
Then, I overrode +[UIView layerClass] in the view that uses my CAShapeLayer subclass.
Lastly, the view using my layer was added as a subview to a UIScrollView.
To reference self.layer (in the view) with the correct class (GridLayer vs CALayer), I created the following property:
#property (nonatomic, weak, readonly, getter=graphLayer) GraphLayer *graphLayer;
and getter
- (GraphLayer *)graphLayer {
return (GraphLayer *)self.layer;
}

Custom animatable property returns NSNull?

Only one day left on the bounty!
Can you have a custom animatable property work with the UIView animateWithDuration block syntax?
I've been working on creating my own implicit animation for a custom animatable property on a custom CALayer.
I have set the backing layer of a custom UIView and can use a CATransaction to animate the custom property (in the custom example I am changing the color of a circle with a custom property circleColor).
I know that UIView turns off animatable properties by returning NSNull from the actionForLayer:forKey: selector method on UIView. Then when the property is wrapped in the UIView animateWithDuration block the UIView will return the animation.
So this will work with backgroundColor, opacity, etc. However, my custom property circleColor still returns NSNull.
I hope I provided enough context to the question. Thanks!
So this question has been open for a long-time and I'm concluding that it isn't possible. The code below shows how I would turn on the custom property to be detected within the block. This method is overridden in UIView.
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
NSLog(#"%s - event: %#", __PRETTY_FUNCTION__, event);
id<CAAction> action = [super actionForLayer:layer forKey:event];
if ([event isEqualToString:#"circleColor"] && (nil == action || (id)[NSNull null] == action)) {
return [CABasicAnimation animationWithKeyPath:event];
}
return action;
}
The code allows you to animate within the UIView block however there is no way for it to detect that it is within a UIView block. So if you use this code then it will even animate outside of the UIView block which defeats the purpose. I'll monitor for other people's responses, but the only options it seems is to make your own custom UIView animation block system which will allow you to add custom animatable properties. I would imagine that Facebook's Pop framework would be a great alternative solution.

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

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.

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.

Resources