Reputation: 2707
I am wondering how one might animate a CALayer's bounds so, on each bounds change, the layer calls drawInContext:
. I've tried the 2 following methods on my CALayer subclass:
needsDisplayOnBoundsChange
to YES
YES
for the + (BOOL)needsDisplayForKey:(NSString*)key
for the bounds
keyNeither work. CALayer seems determined to use the layer's original contents and simply scale them according to contentsGravity
(which, I assume, is for performance.) Is their a workaround for this or am I missing something obvious?
EDIT: And, incidentally, I noticed that my custom CALayer
subclass is not calling initWithLayer:
to create a presentationLayer
- weird.
Thanks in advance, Sam
Upvotes: 17
Views: 4751
Reputation:
I'm running into the same problem recently. This is what I found out:
There is a trick you can do it, that is animating a shadow copy of bounds like:
var shadowBounds: CGRect {
get { return bounds }
set { bounds = newValue}
}
then override CALayer's +needsDisplayForKey:.
However, this MAY NOT be what you want to do if your drawing depends on bounds. As you have already noticed, core animation simply scales the contents of layer to animate bounds. This is true even if you do the above trick, that is, the contents are scaled even if the bounds changed during animation. The result is your animation of drawings looks inconsistent.
How to resolve it? Since the content is scaled, you can calculate the values of custom variables determining your drawing by reverse-scaling them so that your drawing on the final but scaled content looks the same as the original and unscaled one, then set the fromValues to these values, the toValues to their old values, animate them at the same time with bounds. If the final values are to be changed, set the toValues to these final values. You must animate at least one custom variable so as to causing the redraw.
Upvotes: 0
Reputation: 12819
I don't know if this entirely qualifies as a solution to your question, but was able to get this to work.
I first pre-drew my contents image into a CGImageRef
.
I then overrode the -display
method of my layer INSTEAD OF -drawInContext:
. In it I set contents
to the pre-rendered CGImage, and it worked.
Finally, your layer also needs to change the default contentsGravity
to something like @"left"
to avoid the contents image being drawn scaled.
The problem I was having was that the context getting passed to -drawInContext:
was of the starting size of the layer, not the final post-animation size. (You can check this with the CGBitmapContextGetWidth
and CGBitmapContextGetHeight
methods.)
My methods are still only called once for the entire animation, but setting the layer's contents directly with the -display
method allows you to pass an image larger than the visible bounds. The drawInContext:
method does not allow this, as you cannot draw outside the bounds of the CGContext context.
For more about the difference between the different layer drawing methods, see http://www.apeth.com/iOSBook/ch16.html
Upvotes: 0
Reputation: 19251
You can use the technique outlined here: override CALayer's +needsDisplayForKey:
method and it will redraw its content at every step of the animation.
Upvotes: 7
Reputation: 1189
This is your custom class:
@implementation MyLayer
-(id)init
{
self = [super init];
if (self != nil)
self.actions = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNull null], @"bounds",
nil];
return self;
}
-(void)drawInContext:(CGContextRef)context
{
CGContextSetRGBFillColor(context,
drand48(),
drand48(),
drand48(),
1);
CGContextFillRect(context,
CGContextGetClipBoundingBox(context));
}
+(BOOL)needsDisplayForKey:(NSString*)key
{
if ([key isEqualToString:@"bounds"])
return YES;
return [super needsDisplayForKey:key];
}
@end
These are additions to xcode 4.2 default template:
-(BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
// create and add layer
MyLayer *layer = [MyLayer layer];
[self.window.layer addSublayer:layer];
[self performSelector:@selector(changeBounds:)
withObject:layer];
return YES;
}
-(void)changeBounds:(MyLayer*)layer
{
// change bounds
layer.bounds = CGRectMake(0, 0,
drand48() * CGRectGetWidth(self.window.bounds),
drand48() * CGRectGetHeight(self.window.bounds));
// call "when idle"
[self performSelector:@selector(changeBounds:)
withObject:layer
afterDelay:0];
}
----------------- edited:
Ok... this is not what you asked for :) Sorry :|
----------------- edited(2):
And why would you need something like that? (void)display
may be used, but documentation says it is there for setting self.contents
...
Upvotes: -2
Reputation: 12421
I'm not sure that setting the viewFlags would be effective. The second solution definitely won't work:
The default implementation returns NO. Subclasses should * call super for properties defined by the superclass. (For example, * do not try to return YES for properties implemented by CALayer, * doing will have undefined results.)
You need to set the view's content mode to UIViewContentModeRedraw:
UIViewContentModeRedraw, //redraw on bounds change (calls -setNeedsDisplay)
Check out Apple's documentation on providing content with CALayer's. They recommend using the CALayer's delegate property instead of subclass, which might be a lot easier than what you're trying now.
Upvotes: 1