Animate a UIView's CoreGraphics/drawRect content - ios

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.

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

iOS, How to alternate simple UIView instances of graphics with animateWithDuration?

I'm not sure how to proceed. I want to animate a slight graphics changes. I need this to happen in a loop every 2 seconds.
I have some drawing code in a class within a drawrect method. I pass in a style parameter with a custom method initWithFrame.
Within my animation animateWithDuration block, I thought I could just removeFromSuperview then addSubview different styled instances.
I know this sounds like the wrong approach.
However, I wasn't sure how else to do this.
Can anyone give me some options / point me at an example ?
To your original question about whether you should remove and re-add the view, typically you wouldn't do that (as that's a little inefficient). I usually have a public property in my custom view that (a) the view controller can set; and that (b) is used by the drawRect method. I then implement a custom setter that not only saves the ivar backing the property, but calls setNeedsDisplay, too (which will trigger drawRect to be called for you).
You haven't described what is changing in the view, but let me give you a random example. Let's say you have a drawRect that draws a circle of a given radius. Then my UIView subclass might have a property for this CGFloat, e.g.:
#property (nonatomic) CGFloat radius;
And then I might implement a custom setter which will not only update the ivar, like usual, but also triggers the re-calling of drawRect:
- (void) setRadius:(CGFloat)newRadius
{
_radius = newRadius; //note, I set the ivar backing the property; do not try self.radius = ..., because that will call this same method with infinite recursion
[self setNeedsDisplay];
}
Then, when my view controller wants to update the view, it can simply do the following:
customView.radius = 27.5;
Or, if I want to animate the changing of this every two seconds, fading to the new position, every two seconds, I'd wrap the changing of this property in a transitionWithView block:
[UIView transitionWithView:customView
duration:0.3
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
customView.radius = 27.5;
}
completion:nil];
Now, clearly this is a random example where this property was a CGFloat, but you can do this with any custom property that your drawRect needs to reflect the new rendering of the screen.

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.

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];

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