image rotation, covers the button - objective-c

I want to rotate the image around the x-axis from left to right. The problem is that when you rotate the image covers the button located on the top
Run animation
[AnimationUtil rotationRightToLeftForView:image andDuration:1];
Animation metod
+(void) rotationRightToLeftForView:(UIView *)flipView andDuration:(NSTimeInterval)duration{
// Remove existing animations before stating new animation
[flipView.layer removeAllAnimations];
// Make sure view is visible
flipView.hidden = NO;
// show 1/2 animation
//flipView.layer.doubleSided = NO;
// disable the view so it’s not doing anythign while animating
flipView.userInteractionEnabled = NO;
// Set the CALayer anchorPoint to the left edge and
// translate the button to account for the new
// anchorPoint. In case you want to reuse the animation
// for this button, we only do the translation and
// anchor point setting once.
if (flipView.layer.anchorPoint.x != 0.0f) {
flipView.layer.anchorPoint = CGPointMake(0.0f, 0.5f);
flipView.center = CGPointMake(flipView.center.x-flipView.bounds.size.width/2.0f, flipView.center.y);
}
// create an animation to hold the page turning
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
transformAnimation.removedOnCompletion = NO;
transformAnimation.duration = duration;
transformAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// start the animation from the current state
transformAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
// this is the basic rotation by 180 degree along the y-axis M_PI
CATransform3D endTransform = CATransform3DMakeRotation(radians(180.0), 0.0f, -1.0f, 0.0f);
transformAnimation.toValue = [NSValue valueWithCATransform3D:endTransform];
// Create an animation group to hold the rotation
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
// Set self as the delegate to receive notification when the animation finishes
theGroup.delegate = self;
theGroup.duration = duration;
// CAAnimation-objects support arbitrary Key-Value pairs, we add the UIView tag
// to identify the animation later when it finishes
[theGroup setValue:[NSNumber numberWithInt:flipView.tag] forKey:#"viewFlipTag"];
// Here you could add other animations to the array
theGroup.animations = [NSArray arrayWithObjects:transformAnimation, nil];
theGroup.removedOnCompletion = NO;
// Add the animation group to the layer
[flipView.layer addAnimation:theGroup forKey:#"flipView"];
}

The decision follows:
Create mask (image) Color = black, make region Size = Button.size Color = transparent
image name = "mask2.png"
button = ...;
ImageView = ...;
parent_view = ...;
UIImage *_maskingImage = [UIImage imageNamed:#"mask2"];
CALayer *_maskingLayer = [CALayer layer];
_maskingLayer.frame = parent_View.bounds;
[_maskingLayer setContents:(id)[_maskingImage CGImage]];
CALayer *layerForImage = [[CALayer alloc] init];
layerForImage.frame = parent_View.bounds;
[layerForImage setMask:_maskingLayer];
[layerForImage addSublayer:ImageView.layer];
[parent_View.layer addSublayer:layerForImage];
[parent_View addSubView:button];

Related

Custom Spinner class with rotation animation problem

I programmed my own view containing an imageview which should be rotating. Here is my rotation animation:
- (void)startPropeller
{
//_movablePropeller = [[UIImageView alloc] initWithFrame:self.frame];
//_movablePropeller.image = [UIImage imageNamed:#"MovablePropeller"];
//[self addSubview:self.movablePropeller];
self.hidden = NO;
CABasicAnimation *rotation;
rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotation.fromValue = [NSNumber numberWithFloat:0.0f];
rotation.toValue = [NSNumber numberWithFloat:(2 * M_PI)];
rotation.cumulative = true;
rotation.duration = 1.2f; // Speed
rotation.repeatCount = INFINITY; // Repeat forever. Can be a finite number.
[self.movablePropeller.layer removeAllAnimations];
[self.movablePropeller.layer addAnimation:rotation forKey:#"Spin"];
}
And here is how I start it:
self.loadingPropeller = [[FMLoadingPropeller alloc] initWithFrame:self.view.frame andStyle:LoadingPropellerStyleNoBackground];
self.loadingPropeller.center=self.view.center;
[self.view addSubview:self.loadingPropeller];
[self.loadingPropeller startPropeller];
Problem is: Without any further code. The propeller is not rotating. So I was able to solve it by adding this code into my class implementing to rotating propeller spinner:
-(void)viewDidAppear:(BOOL)animated
{
if(!self.loadingPropeller.hidden){
[self.loadingPropeller startPropeller];
}
}
But I don't like that too much. Isn't it possible to add some code within the Propeller class to solve this issue automatically, without having to add also code in every class in the viewDidAppear method?
The code that doesn't work does two essential things: adding the spinner to the view hierarchy and positioning it. My guess is that the failure is due to positioning it before layout has happened. Try this:
// in viewDidLoad of the containing vc...
self.loadingPropeller = [[FMLoadingPropeller alloc] initWithFrame:CGRectZero andStyle:LoadingPropellerStyleNoBackground];
[self.view addSubview:self.loadingPropeller];
// within or after viewDidLayoutSubviews...
// (make sure to call super for any of these hooks)
self.loadingPropeller.frame = self.view.bounds;
self.loadingPropeller.center = self.view.center;
// within or after viewDidAppear (as you have it)...
[self.loadingPropeller startPropeller];

How to shrink a rectangular round cornered UIButton to a circular UIButton?

With the help of this i was able to shrink the UIButton but atlast i want the UIButton to get rounded.Please help me to get the desired animation in sign up button. The code snippet is :
Follow the link : https://www.dropbox.com/s/rh4tdub3zabxp2j/shot.gif?dl=0
self.buttonShrink = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
self.buttonShrink.duration = .2f;
self.buttonShrink.values = #[[NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.9,1,1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.8,1,1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.7,1,1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.6,1,1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.5,1,1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.4,1,1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(.3,1,1)]];
self.buttonShrink.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
self.sampleButton.transform = CGAffineTransformMakeScale(0,0);
self.sampleButton.alpha = 1;
[self.sampleButton.layer addAnimation:self.buttonShrink forKey:#"buttonScale"];
[self.sampleButton setUserInteractionEnabled:NO];
I did some tinkering and got pretty decent results.
EDIT:
I just uploaded a demo project to GitHub called MorphingButton (link) that generates the animation below:
Here's what I did:
I created a normal iOS 8 button in IB (no outline at all) and connected an outlet and an action to it.
I added height and width constraints.
I added code to set the borderColor, borderWidth, and cornerRadius of the button's layer to give it a rounded corner look. This would take some adjustment to make it look like a real rounded rectangle button.
In the IBAction for the button, switch back and forth between making it round and making it rectangular.
To make the button round:
Use a UIView animateWithDuration method call to set the button's
height constraint to it's width constraint (making it square) and invoke layoutWithNeeded()
Use aCABasicAnimation to animate the button's layer's corner radius to 1/2
the button width.
To make the button rectangular:
Use a UIView animateWithDuration method call to set the button's
height constraint to it's starting height constraint
Use aCABasicAnimation to animate the button's layer's corner radius to 10 (which looks pretty good for a rounded rectangle button.)
The IBAction and viewDidLoad code would look like this in Objective-C:
- (void)viewDidLoad
{
oldHeight = buttonHeightConstraint.constant;
buttonIsRound = FALSE;
[super viewDidLoad];
animationDuration = 0.5;
}
- (IBAction)handleButton:(id)sender
{
CGFloat newHeight;
CGFloat newCornerRadius;
NSLog(#"Entering %s", __PRETTY_FUNCTION__);
if (buttonIsRound)
{
//If the button is currently round,
//go back to the old height/corner radius
newHeight = oldHeight;
newCornerRadius = 10;
}
else
{
//It isn't round now,
//so make it's height and width the same
//and set the corner radius to 1/2 the width
newHeight = buttonWidthConstraint.constant;
newCornerRadius = buttonWidthConstraint.constant/2;
}
[UIView animateWithDuration: animationDuration
animations:^
{
buttonHeightConstraint.constant = newHeight;
[button layoutIfNeeded];
}];
CABasicAnimation *cornerAnimation = [[CABasicAnimation alloc] init];
cornerAnimation.keyPath = #"cornerRadius";
cornerAnimation.fromValue = #(button.layer.cornerRadius);
cornerAnimation.toValue = #(newCornerRadius);
cornerAnimation.duration = animationDuration;
[button.layer addAnimation: cornerAnimation forKey: #"woof"];
button.layer.cornerRadius = newCornerRadius;
buttonIsRound = !buttonIsRound;
}
The Swift IBAction code for the button looks like this:
#IBAction func handleButton(sender: AnyObject)
{
if !buttonIsRound
{
UIView.animateWithDuration(animationDuration)
{
self.buttonHeightConstraint.constant = self.buttonWidthConstraint.constant
self.button.layoutIfNeeded()
self.buttonIsRound = true
}
let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius")
cornerAnimation.fromValue = button.layer.cornerRadius
cornerAnimation.toValue = self.buttonWidthConstraint.constant / 2.0
cornerAnimation.duration = animationDuration
button.layer.addAnimation(cornerAnimation, forKey: "woof")
button.layer.cornerRadius = self.buttonWidthConstraint.constant / 2.0
}
else
{
UIView.animateWithDuration(animationDuration)
{
self.buttonHeightConstraint.constant = self.oldHeight
self.button.layoutIfNeeded()
self.buttonIsRound = false
}
let cornerAnimation = CABasicAnimation(keyPath: "cornerRadius")
cornerAnimation.fromValue = self.buttonWidthConstraint.constant / 2.0
cornerAnimation.toValue = 10
cornerAnimation.duration = animationDuration
button.layer.addAnimation(cornerAnimation, forKey: "woof")
button.layer.cornerRadius = 10
}
}
I never used this for shrinking but as you are using button layer so why can not you use it cornerRadius. I'm not sure suggestion is ok or not??

Method to resize CALayer frame on window resize?

I draw a series of images to various CALayer sublayers, then add those sublayers to a superlayer:
- (void)renderImagesFromArray:(NSArray *)array {
CALayer *superLayer = [CALayer layer];
for (id object in array) {
CALayer* subLayer = [CALayer layer];
// Disregard...
NSURL *path = [NSURL fileURLWithPathComponents:#[NSHomeDirectory(), #"Desktop", object]];
NSImage *image = [[NSImage alloc] initWithContentsOfURL:path];
[self positionImage:image layer:subLayer];
subLayer.contents = image;
subLayer.hidden = YES;
[superLayer addSublayer:subLayer];
}
[self.view setLayer:superLayer];
[self.view setWantsLayer:YES];
// Show top layer
CALayer *top = superLayer.sublayers[0];
top.hidden = NO;
}
I then call [self positionImage: layer:] to stretch the CALayer to it's maximum bounds (essentially using the algorithm for the CSS cover property), and position it in the center of the window:
- (void)positionImage:(NSImage *)image layer:(CALayer *)layer{
float imageWidth = image.size.width;
float imageHeight = image.size.height;
float frameWidth = self.view.frame.size.width;
float frameHeight = self.view.frame.size.height;
float aspectRatioFrame = frameWidth/frameHeight;
float aspectRatioImage = imageWidth/imageHeight;
float computedImageWidth;
float computedImageHeight;
float verticalSpace;
float horizontalSpace;
if (aspectRatioImage <= aspectRatioFrame){
computedImageWidth = frameHeight * aspectRatioImage;
computedImageHeight = frameHeight;
verticalSpace = 0;
horizontalSpace = (frameWidth - computedImageWidth)/2;
} else {
computedImageWidth = frameWidth;
computedImageHeight = frameWidth / aspectRatioImage;
horizontalSpace = 0;
verticalSpace = (frameHeight - computedImageHeight)/2;
}
[CATransaction flush];
[CATransaction begin];
CATransaction.disableActions = YES;
layer.frame = CGRectMake(horizontalSpace, verticalSpace, computedImageWidth, computedImageHeight);
[CATransaction commit];
}
This all works fine, except when the window gets resized. I solved this (in a very ugly way) by subclassing NSView, then implementing the only method that was actually called when the window resized, viewWillDraw::
- (void)viewWillDraw{
[super viewWillDraw];
[self redraw];
}
- (void)redraw{
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
CALayer *superLayer = self.layer;
NSArray *sublayers = superLayer.sublayers;
NSImage *image;
CALayer *current;
for (CALayer *view in sublayers){
if (!view.isHidden){
current = view;
image = view.contents;
}
}
[appDelegate positionImage:image layer:current];
}
So... what's the right way to do this? viewWillDraw: get's called too many times which means I have to do unnecessary and redundant calculations, and I can't use viewWillStartLiveResize: because I need to constantly keep the image in its correct position. What am I overlooking?
Peter Hosey was right; my original method was clunky, and I shouldn't have been overriding setNeedsDisplayInRect:. I first made sure that I was using an auto layout in my app, then implemented the following:
subLayer.layoutManager = [CAConstraintLayoutManager layoutManager];
subLayer.autoresizingMask = kCALayerHeightSizable | kCALayerWidthSizable;
subLayer.contentsGravity = kCAGravityResizeAspect;
Basically, I set the sublayer's autoResizingMask to stretch both horizontally and vertically, and then set contentsGravity to preserve the aspect ratio.
That last variable I found by chance, but it's worth noting that you can only use a few contentsGravity constants if, like in my case, you're setting an NSImage as the layer's contents:
That method creates an image that is suited for use as the contents of a layer and that is supports all of the layer’s gravity modes. By contrast, the NSImage class supports only the kCAGravityResize, kCAGravityResizeAspect, and kCAGravityResizeAspectFill modes.
Always fun when a complicated solution can be simplified to 3 lines of code.

Core Animation: Make content follow shadow path

I have an animated shadow path of a CALayer:
CABasicAnimation* shadowAnimation = [CABasicAnimation animationWithKeyPath: #"shadowPath"];
shadowAnimation.duration = duration;
shadowAnimation.timingFunction = function;
shadowAnimation.fromValue = (id) panelView.layer.shadowPath;
panelView.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect: CGRectSetOriginM20 (panelView.bounds, CGPointZero) cornerRadius: cornerRadius].CGPath;
[panelView.layer addAnimation: shadowAnimation forKey: #"shadow"];
panelView.layer.shadowOpacity = 1.0;
I need to recreate the same effect using an asset by settings the CALayer's content property to a UIImage. Is there a way to make the bounds follow the same animation as the shadow?
No, you can't make the content follow the shadow path.
Instead you have to animate the shadow path and the bounds together. This can be done in an animation group so that you only can configure the timing function and duration on the group.
CAAnimationGroup* shadowAndBounds = [CAAnimationGroup animation];
shadowAndBounds.duration = duration;
shadowAndBounds.timingFunction = function;
CABasicAnimation* shadowAnimation = [CABasicAnimation animationWithKeyPath: #"shadowPath"];
// Set to and from value for the shadow path ...
CABasicAnimation* boundsAnimation = [CABasicAnimation animationWithKeyPath: #"bounds"];
// Set to and from value for the bounds ...
shadowAndBounds.animations = #[ shadowAnimation, boundsAnimation ];
[panelView.layer addAnimation: shadowAndBounds forKey: #"shadowAndBounds"];

How to add an animated layer at a specific index

I am adding two CAText layers to a view and animating one of them. I want to animate one layer above the other but it doesn't get positioned correctly in the layer hierarchy until the animation has finished. Can anyone see what I have done wrong? The animation works, it is just running behind 'topcharlayer2' until the animation has finished.
- (CABasicAnimation *)topCharFlap
{
CABasicAnimation *flipAnimation;
flipAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
flipAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(1.57f, 1, 0, 0)];
flipAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(0.0, 1, 0, 0)];
flipAnimation.autoreverses = NO;
flipAnimation.duration = 0.5f;
flipAnimation.repeatCount = 10;
return flipAnimation;
}
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
[self setBackgroundColor:[UIColor clearColor]]; //makes this view transparent other than what is drawn.
[self initChar];
}
return self;
}
static CATransform3D CATransform3DMakePerspective(CGFloat z)
{
CATransform3D t = CATransform3DIdentity;
t.m34 = - 1. / z;
return t;
}
-(void) initChar
{
UIFont *theFont = [UIFont fontWithName:#"AmericanTypewriter" size:FONT_SIZE];
self.layer.sublayerTransform = CATransform3DMakePerspective(-1000.0f);
topHalfCharLayer2 = [CATextLayer layer];
topHalfCharLayer2.bounds = CGRectMake(0.0f, 0.0f, CHARACTERS_WIDTH, 100.0f);
topHalfCharLayer2.string = #"R";
topHalfCharLayer2.font = theFont.fontName;
topHalfCharLayer2.fontSize = FONT_SIZE;
topHalfCharLayer2.backgroundColor = [UIColor blackColor].CGColor;
topHalfCharLayer2.position = CGPointMake(CGRectGetMidX(self.bounds),CGRectGetMidY(self.bounds));
topHalfCharLayer2.wrapped = NO;
topHalfCharLayer1 = [CATextLayer layer];
topHalfCharLayer1.bounds = CGRectMake(0.0f, 0.0f, CHARACTERS_WIDTH, 100.0f);
topHalfCharLayer1.string = #"T";
topHalfCharLayer1.font = theFont.fontName;
topHalfCharLayer1.fontSize = FONT_SIZE;
topHalfCharLayer1.backgroundColor = [UIColor redColor].CGColor;
topHalfCharLayer1.position = CGPointMake(CGRectGetMidX(self.bounds),CGRectGetMidY(self.bounds));
topHalfCharLayer1.wrapped = NO;
//topHalfCharLayer1.zPosition = 100;
[topHalfCharLayer1 setAnchorPoint:CGPointMake(0.5f,1.0f)];
[[self layer] addSublayer:topHalfCharLayer1 ];
[[self layer] insertSublayer:topHalfCharLayer2 atIndex:0];
[topHalfCharLayer1 addAnimation:[self topCharFlap] forKey:#"anythingILikeApparently"];
}
The View which contains this code is loaded by a view controller in loadView. The initChar method is called in the view's initWithFrame method. The target is iOS4. I'm not using setWantsLayer as I've read that UIView in iOS is automatically layer backed and doesn't require this.
A couple thoughts come to mind:
Try adding the 'R' layer to the layer hierarchy before you start the animation.
Instead of inserting the 'T' layer at index 1, use [[self layer] addSublayer: topHalfCharLayer1]; to add it and then do the insert for the 'R' layer with [[self layer] insertSublayer:topHalfCharLayer2 atIndex:0];
Have you tried to play with the layer zPosition? This determines the visual appearance of the layers. It doesn't actually shift the layer order, but will change the way they display--e.g. which layers is in front of/behind which.
I would also suggest you remove the animation code until you get the layer view order sorted. Once you've done that, the animation should just work.
If you have further issues, let me know in the comments.
Best regards.
From the quartz-dev apple mailing list:
Generally in a 2D case, addSublayer will draw the new layer above the
previous. However, I believe this implementation mechanism is
independent of zPosition and probably just uses something like
painter's algorithm. But the moment you add zPositions and 3D, I don't
think you can solely rely on layer ordering. But I am actually unclear
if Apple guarantees anything in the case where you have not set
zPositions on your layers but have a 3D transform matrix set.
So, it seems I have to set the zPosition explicitly when applying 3D transforms to layers.
/* Insert 'layer' at position 'idx' in the receiver's sublayers array.
* If 'layer' already has a superlayer, it will be removed before being
* inserted. */
open func insertSublayer(_ layer: CALayer, at idx: UInt32)