I'm animating a layer-backed view that contains several subviews. Everything is laid out using auto layout. Currently, as I animate the view, the subviews are not being scaled along with it:
I'd like the whole thing to be drawn once, at their final size, then scaled as it animates. The layerContentsPlacement and layerContentsRedrawPolicy properties seem to be what I'm looking for, but changing them don't seem to have any effect.
Here's a basic run-through of how I'm doing the animation:
// Add itemView to canvas
NSView *itemView = // ...
itemView.layerContentsPlacement = NSViewLayerContentsPlacementScaleProportionallyToFit;
itemView.layerContentsRedrawPolicy = NSViewLayerContentsRedrawBeforeViewResize;
[parentView addSubview:itemView];
// Add constraints for the final itemView frame ...
// Layout at final state
[parentView layoutSubtreeIfNeeded];
// Set initial animation state
itemView.alphaValue = 0.0f;
itemView.frame = // centered zero'ed frame
// Set final animation state
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
context.duration = 0.25f;
context.allowsImplicitAnimation = YES;
itemView.alphaValue = 1.0f;
[parentView layoutSubtreeIfNeeded];
} completionHandler:nil];
Thanks to David's comment I switched to using a transform on the view's layer.
The basic idea is to create and layout your view as you normally would, then create and add some CA animations to the view's layer.
// Add item view to canvas
NSView *itemView = // ...
[parentView addSubview:itemView];
// Add constraints for the final item view positioning
// Layout at final state
[parentView layoutSubtreeIfNeeded];
// Animate
CABasicAnimation *animation = [CABasicAnimation animation];
CATransform3D transform = CATransform3DMakeScale(0.5f, 0.5f, 0.5f);
animation.fromValue = [NSValue valueWithCATransform3D:transform];
animation.duration = 1.0f;
[itemView.layer addAnimation:animation forKey:#"transform"];
// create any other animations...
Related
Is it possible to modify the anchorPoint property on the root CALayer of a layer-backed NSView?
I have a view called myView and it seems every time I set the anchorPoint, it gets overridden in the next run loop. I am doing this:
NSView *myView = [[myView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
//set the root layer
myView.layer = [CALayer layer];
myView.wantsLayer = YES;
//gets overridden on the next run loop
myView.layer.anchorPoint = CGPointMake(1,1);
On 10.8, AppKit will control the following properties on a CALayer
(both when "layer-hosted" or "layer-backed"): geometryFlipped, bounds,
frame (implied), position, anchorPoint, transform, shadow*, hidden,
filters, and compositingFilter. … Use the appropriate NSView cover
methods to change these properties.
Basically it will set the anchor to [0,0] from [0.5,0.5], to account for this i uses something like :
+(void) accountForLowerLeftAnchor:(CALayer*)layer
{
CGRect frame = layer.frame;
CGPoint center = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
layer.position = center;
layer.anchorPoint = CGPointMake(0.5, 0.5);
}
I add to the scrollview my view with content as a documenView and use this code to fadeout my documentView:
- (IBAction)deleteAllRules:(id)sender {
NSViewAnimation * deleteListOfRulesViewAnimation;
NSRect viewRect;
NSMutableDictionary* animDict;
// Create the attributes dictionary
animDict = [NSMutableDictionary dictionaryWithCapacity:3];
viewRect = listOfRulesView.frame;
// Specify which view to modify.
[animDict setObject:listOfRulesView forKey:NSViewAnimationTargetKey];
// Specify the starting position of the view.
[animDict setObject:[NSValue valueWithRect:viewRect] forKey:NSViewAnimationStartFrameKey];
// Shrink the view from its current size to nothing.
NSRect viewZeroSize = listOfRulesView.frame;
viewZeroSize.size.width = 0;
viewZeroSize.size.height = 0;
[animDict setObject:[NSValue valueWithRect:viewZeroSize] forKey:NSViewAnimationEndFrameKey];
// Set this view to fade out
[animDict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
// Create the view animation object.
deleteListOfRulesViewAnimation = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray
arrayWithObjects:animDict, nil]];
// Set some additional attributes for the animation.
[deleteListOfRulesViewAnimation setDuration:1.5]; // One and a half seconds.
[deleteListOfRulesViewAnimation setAnimationCurve:NSAnimationEaseIn];
[deleteListOfRulesViewAnimation setDelegate:self];
// Run the animation.
[deleteListOfRulesViewAnimation startAnimation];
// The animation has finished, so go ahead and release it.
[deleteListOfRulesViewAnimation release];
}
but view hide with out fadeout effect. Why?
It's recommended to use Core Animation for 10.5+ code.
The simplest way to use Core Animation is to use the animator proxy. For example you can do the fade out effect by animating the alpha value of the view :
[[myView animator] setAlpaValue:0.0f]
You can change the animation duration using:
[[NSAnimationContext currentContext] setDuration:0.4f]
How to shadow documentView in NSScrollView?
The effect look likes iBook Author:
You need to inset the content in your document view to allow space for the shadow to be displayed, then layer back the view and set a shadow on it. Example:
view.wantsLayer = YES;
NSShadow *shadow = [NSShadow new];
shadow.shadowColor = [NSColor blackColor]
shadow.shadowBlurRadius = 4.f;
shadow.shadowOffset = NSMakeSize(0.f, -5.f);
view.shadow = shadow;
The NSScrollView contentView is an NSView subclass, which has a shadow field, if you create a shadow object and assign it to this field, the view will automatically show a drop shadow when drawn
NSShadow* shadow = [[NSShadow alloc] init];
shadow.shadowBlurRadius = 2; //set how many pixels the shadow has
shadow.shadowOffset = NSMakeSize(2, -2); //the distance from the view the shadow is dropped
shadow.shadowColor = [NSColor blackColor];
self.scrollView.contentView.shadow = shadow;
This works because all views when are drawn on drawRect use this shadow property by using [shadow set].
doing [shadow set] during a draw operation makes whatever is drawn after that to be replicated underneath
I'm new to entering posts on stack overflow but I had the same issue and have solved it so I thought after searching the net for hours to find a solution it would be nice to answer it.
My solution is to create a subclass for NSClipView with the following code for drawRect...
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
NSRect childRect = [[self documentView] frame];
[NSGraphicsContext saveGraphicsState];
// Create the shadow below and to the right of the shape.
NSShadow* theShadow = [[NSShadow alloc] init];
[theShadow setShadowOffset:NSMakeSize(4.0, -4.0)];
[theShadow setShadowBlurRadius:3.0];
// Use a partially transparent color for shapes that overlap.
[theShadow setShadowColor:[[NSColor grayColor]
colorWithAlphaComponent:0.95f]];
[theShadow set];
[[self backgroundColor] setFill];
NSRectFill(childRect);
// Draw your custom content here. Anything you draw
// automatically has the shadow effect applied to it.
[NSGraphicsContext restoreGraphicsState];
}
You then need to create an instance of the subclass and set it with the setContentView selector.
You also need to repaint the clip view every time the content view size changes. If you have your content view set up to change in terms of canvas size when the user wants then unless you repaint the clip view some nasty shadow marks will left behind.
You don't need to mess about with clips as others have suggested.
Hope it helps!
I try to use the anchorPoint in a layer hierarchy to move a layer with its sublayers. Unfortunately the sublayer did not move together with the root layer. In a custom NSView is set up my layer hierarchy like in the following snippet.
CALayer * rootLayer;
rootLayer = [[CALayer layer] retain];
rootLayer.position = CGPointMake(...);
[self.layer addSublayer:rootLayer];
subLayer = [[MapLayer layer] retain];
subLayer.position = CGPointMake(...);
[rootLayer addSublayer:baseLayer];
While handling a mouse event I want to set the anchorPoint of the rootLayer to move the whole layer hierarchy:
rootLayer.anchorPoint = CGPoint(0.7, 0.7);
I expect from this call, that the rootLayer moves together with its subLayer so that the anchor point is at the center of the view. What happens is, that the sublayer did not move. Only when I call:
rootLayer.anchorPoint = CGPoint(0.7, 0.7);
subLayer.anchorPoint = CGPoint(0.7, 0.7);
the layers behave as expected.
I thought setting the anchor point of the root layer would be enough. This is used in my Map application for OS X. There I set up the view and use the anchorPoint to move the whole map. With only one sublayer in the custom NSView the application behaves as expected.
Thanks in advance.
UPDATE: After a discussion with a colleague I set the bounds of the rootLayer and now it works.
CALayer * rootLayer;
rootLayer = [[CALayer layer] retain];
rootLayer.bounds = CGRectMake(0, 0, 256, 256);
rootLayer.position = CGPointMake(...);
[self.layer addSublayer:rootLayer];
// ...
I have been running into some issues with animating multiple CALayers at the same time, and was hoping someone could point me in the right direction.
My app contains an array of CALayer. The position of each layer is set to (previousLayer.position.y + previousLayer.bounds.height), which basically lays them out similar to a table. I then have a method that, every-time it is called, adds a new layer to the stack and sets its Y position is set to 0. The Y positions of all other layers in the array are then offset by the height of the new layer (essentially pushing all old layers down).
What I am having problems with is preventing the adding of new layers until the previous animation has completed. Is there a way to tell when an implicit animation has finished? Or alternatively, if I use CABasicAnimation and animationDidFinish, is there a way to tell which object finished animating when animationDidFinish is called?
You can set arbitrary values for keys on your animation object. What this means is that you can associate your layer that you're animating with the animation and then query it in -animationDidStop:finished: You create your animation this way:
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"position"];
// set other fields...
[anim setValue:layerToAnimate forKey:#"layer"];
// Start the animation
[layerToAnimate addAnimation:anim forKey:nil];
Then check for that value when the animation stops:
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
{
CALayer *animatedLayer = [animation valueForKey:#"layer"];
// do something with the layer based on some condition...
// spin off the next animation...
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
forKey:kCATransactionDisableActions];
[animatedLayer setPosition:position];
[CATransaction commit];
}
This is explicit animation, but it should give you what you need.
You can try surrounding your code in a CATransaction. Here's how that would look in Swift 3:
CATransaction.begin()
CATransaction.setCompletionBlock({
// run after the animations
})
// animtations
CATransaction.commit()
It turns out that rather than adding the CABasicAnimation directly to the CALayer, I had to add it to the layer's 'actions' dictionary... This leaves the layer at it's final position after the animation ends, but still calls the 'animationDidFinish' method.
-(void)startAnimation {
if(!animating){
animating = YES;
for(int i=0; i<[tweets count]; i++) {
//get the layer
CETweetLayer *currentLayer = [tweets objectAtIndex:i];
//setup the orgin and target y coordinates
float targetY = currentLayer.position.y + offset;
//setup the animation
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"position"];
anim.delegate = self;
currentLayer.actions = [NSDictionary dictionaryWithObject:anim forKey:#"position"];
currentLayer.position = CGPointMake(self.bounds.size.width/2, targetY);
}
}
}
And then...
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
animating = NO;
}