I make two images: one with my spinner image and another with mask image (partially transparent gradient). Imagine this: spinner image is rotating clockwise, in the bottom there is dark gradient (look like haze), rotated spinner partially hided with this gradient. That is my aim.
I add two UIImageView to my scene, but my mask image also influence to background colour and I don't want that. I want that my mask image masking only spinner, so I make this:
- (void)viewDidLoad
{
[super viewDidLoad];
//add mask to UIImageView
CALayer *mask = [CALayer layer];
mask.contents = (id)[[UIImage imageNamed:#"mask.png"] CGImage];
mask.frame = CGRectMake(0, 0, self.spinner.bounds.size.width,self.spinner.bounds.size.height);
self.spinner.layer.mask = mask;
self.spinner.layer.masksToBounds = YES;
//rotate UIImageView (trouble with mask, it must not be rotated)
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat: 2*M_PI];
animation.duration = 20.0f;
animation.repeatCount = INFINITY;
[self.spinner.layer addAnimation:animation forKey:#"Spinner"];
}
As a result I got I spinner with nice mask image, but my mask rotates with my spinner, I want to always clip my mask to bottom of my spinner UIImageView. How can I do this? Or my conception is wrong, if so, give me some directions how can I archive what I want. Feel free to ask. Thanks.
Was tricky for me, but I made it.
Here is what you need to make this: you need a UIView (as a container for UIImageView).
Change this part in source code that I posted below:
CALayer *mask = [CALayer layer];
mask.contents = (id)[[UIImage imageNamed:#"mask.png"] CGImage];
mask.frame = self.yourContainerView.bounds;
self.yourContainerView.layer.mask = mask;
self.yourContainerView.layer.masksToBounds = YES;
That's all. Hope this will help somebody.
Related
I am using this code to create the slide to unlock like animation but I am unable to make it animate from right to left.
How can I make it start from right and animate to left?
-(void)slideToCancel {
CALayer *maskLayer = [CALayer layer];
// Mask image ends with 0.15 opacity on both sides. Set the background color of the layer
// to the same value so the layer can extend the mask image.
maskLayer.backgroundColor = [[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.50f] CGColor];
maskLayer.contents = (id)[[UIImage imageNamed:#"slidetocancel.png"] CGImage];
// Center the mask image on twice the width of the text layer, so it starts to the left
// of the text layer and moves to its right when we translate it by width.
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.frame = CGRectMake(_slideToCancelLbl.frame.size.width * -1, 0.0f, _slideToCancelLbl.frame.size.width * 2, _slideToCancelLbl.frame.size.height);
// Animate the mask layer's horizontal position
CABasicAnimation *maskAnim = [CABasicAnimation animationWithKeyPath:#"position.x"];
maskAnim.byValue = [NSNumber numberWithFloat:_slideToCancelLbl.frame.size.width];
maskAnim.repeatCount = 1e100f;
maskAnim.duration = 1.5f;
[maskLayer addAnimation:maskAnim forKey:#"slideAnim"];
_slideToCancelLbl.layer.mask = maskLayer;
}
You should be able to simply use a negative byValue. However, the code you've posted is a canned animation, not one that responds to a slide gesture.
I need help with core animation on iOS
I have the following view
I need to animate curve appearance from left to right. This step was achieved by animationWithKeyPath:#"strokeEnd", it works and all is fine.
Also I need to animate the gradient appearance under the curve, which goes from left to right along with the curve appearance
Here is the cut from reference video about the appearance I need to achieve
How can I achieve this?
Layers and paths I have:
(1) gradient curve line - uishapelayer
colored gradient - cagradient layer having (1) as mask
(2) closed path layer - path along the (1) curve, but with [path closePath];
gray gradient - cagradient having (2) as mask
(3) animationLayer - layer, added as [self.layer addSublayer:animationLayer]
(3) has colored gradien and gray gradient as sublayers
initWithFrame:
_animationLayer = [CALayer new];
_animationLayer.frame = frame;
_animationLayer.masksToBounds = YES;
[self.layer addSublayer:_animationLayer];
_path = [self characteristicsGraph:frame];
_pathLayer = [CAShapeLayer new];
_pathLayer.frame = frame;
_pathLayer.path = _path.CGPath;
_pathLayer.strokeColor = [color CGColor];
_pathLayer.fillColor = nil;
_pathLayer.lineWidth = lineW;
_fillPathLayer = [CAShapeLayer new];
_fillPathLayer.frame = frame;
_fillPathLayer.path = [self closePath:_path].CGPath;
_fillPathLayer.strokeColor = [color CGColor];
_fillPathLayer.fillColor = [color CGColor];
_fillPathLayer.masksToBounds = YES;
_fillGradient = [self gradient:frame];
_fillGradient.frame = frame;
_fillGradient.mask = _fillPathLayer;
_lineGradient = [self lineGradient:frame];
_lineGradient.frame = frame;
_lineGradient.mask = _pathLayer;
[_animationLayer addSublayer:_fillGradient];
[_animationLayer addSublayer:_lineGradient];
pathLayer animation (animates colored gradient appearance):
self.currentAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
self.currentAnimation.fromValue = #(0);
self.currentAnimation.toValue = #(1);
self.currentAnimation.duration = .5;
[self.pathLayer addAnimation:self.currentAnimation forKey:animationKey];
Gray gradient is static, can't animate it.
Will be grateful for help
You could make a mask layer for the gradient layer - just make it a rectangle that expands as the line animates.
The other problem I noticed if you want to be 100% true to the video is that your gradient will need to be angled along the trend of the chart - notice their chart trends up, and the gradient angle matches.
I am using Swift 1.2 and my goal is to animate an image mask over a static UIImage. What I have implemented is a swift version of masking an image that I originally found in Objective-C.
func maskImage(image: UIImage, mask: UIImage) -> UIImage! {
let maskRef = mask.CGImage;
let mask = CGImageMaskCreate(
CGImageGetWidth(maskRef),
CGImageGetHeight(maskRef),
CGImageGetBitsPerComponent(maskRef),
CGImageGetBitsPerPixel(maskRef),
CGImageGetBytesPerRow(maskRef),
CGImageGetDataProvider(maskRef), nil, false);
let masked = CGImageCreateWithMask(image.CGImage, mask);
let retImage = UIImage(CGImage: masked);
return retImage;
}
It works great! However, putting it in motion is my challenge.
Is there a way to either iteratively apply the mask with a different horizontal offset or a better way to approach this problem entirely - perhaps with a CALayer implementation?
Thanks for your help!
EDIT: Based on what was posted as an answer, I added this:
let image = UIImage(named: "clouds");
let imageView = UIImageView(image: image);
let layer = CALayer();
layer.contents = UIImage(named: "alpha-mask")?.CGImage;
layer.bounds = CGRectMake(0, 0, image.size.width, image.size.height);
// For other folks learning, this did not work
//let animation = CABasicAnimation(keyPath: "bounds.origin.x");
// This does work
let animation = CABasicAnimation(keyPath: "position.x");
animation.duration = 2;
animation.delegate = self;
animation.fillMode = kCAFillModeForwards;
animation.repeatCount = 0;
animation.fromValue = 0.0;
animation.toValue = image.size.width;
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear);
animation.removedOnCompletion = false;
layer.addAnimation(animation, forKey: "transform");
imageView.layer.mask = layer;
self.addSubview(imageView);
I am able to see the alpha mask properly, but the animation does not work. Any ideas?
EDIT: I modified the code above and it works! I needed to make the keyPath position.x. See above
You do indeed want to use a CALayer - or rather, a CAShapeLayer.
You can create a CAShapeLayer and install it as as the mask on another layer.
You can create a CAAnimation that animates changes to the shape layer's path, or you can animate changes to the layer's strokeStart and/or strokeEnd properties.
If you animate the path, the one rule you want to follow is to make sure that the starting and ending path have the same number and type of control points. Otherwise the animation is "undefined", and the results can be very strange.
I have a development blog post that outlines how it's done:
http://wareto.com/using-core-animation-groups-to-create-animation-sequences-2
It's primarily about using CAAnimationGroups, but it also includes a working example of animating changes to a CAShapeLayer that's used as the mask of an image view's layer.
Below is a GIF of the mask animation that it creates - a "clock wipe" that shows and hides an image view:
Unfortunately it's written in Objective-C, but the Core Animation calls are nearly identical in Swift. Let me know if you have any problems figuring out how to adapt it.
The meat of the animation code is this method:
- (IBAction)doMaskAnimation:(id)sender;
{
waretoLogoLarge.hidden = FALSE;//Show the image view
//Create a shape layer that we will use as a mask for the waretoLogoLarge image view
CAShapeLayer *maskLayer = [CAShapeLayer layer];
CGFloat maskHeight = waretoLogoLarge.layer.bounds.size.height;
CGFloat maskWidth = waretoLogoLarge.layer.bounds.size.width;
CGPoint centerPoint;
centerPoint = CGPointMake( maskWidth/2, maskHeight/2);
//Make the radius of our arc large enough to reach into the corners of the image view.
CGFloat radius = sqrtf(maskWidth * maskWidth + maskHeight * maskHeight)/2;
//Don't fill the path, but stroke it in black.
maskLayer.fillColor = [[UIColor clearColor] CGColor];
maskLayer.strokeColor = [[UIColor blackColor] CGColor];
maskLayer.lineWidth = radius; //Make the line thick enough to completely fill the circle we're drawing
CGMutablePathRef arcPath = CGPathCreateMutable();
//Move to the starting point of the arc so there is no initial line connecting to the arc
CGPathMoveToPoint(arcPath, nil, centerPoint.x, centerPoint.y-radius/2);
//Create an arc at 1/2 our circle radius, with a line thickess of the full circle radius
CGPathAddArc(arcPath,
nil,
centerPoint.x,
centerPoint.y,
radius/2,
3*M_PI/2,
-M_PI/2,
YES);
maskLayer.path = arcPath;
//Start with an empty mask path (draw 0% of the arc)
maskLayer.strokeEnd = 0.0;
CFRelease(arcPath);
//Install the mask layer into out image view's layer.
waretoLogoLarge.layer.mask = maskLayer;
//Set our mask layer's frame to the parent layer's bounds.
waretoLogoLarge.layer.mask.frame = waretoLogoLarge.layer.bounds;
//Create an animation that increases the stroke length to 1, then reverses it back to zero.
CABasicAnimation *swipe = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
swipe.duration = 2;
swipe.delegate = self;
[swipe setValue: theBlock forKey: kAnimationCompletionBlock];
swipe.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionLinear];
swipe.fillMode = kCAFillModeForwards;
swipe.removedOnCompletion = NO;
swipe.autoreverses = YES;
swipe.toValue = [NSNumber numberWithFloat: 1.0];
[maskLayer addAnimation: swipe forKey: #"strokeEnd"];
}
I have another blog entry that IS in Swift that shows how to create and animate a pie chart using a CAShapeLayer. That project animates shape, not a mask, but the only real difference is whether you install the shape layer as a regular content layer or as a mask on another layer like the backing layer of an image view.
You can check out that project at this link:
http://wareto.com/swift-piecharts
Goal:
I am drawing a custom shape that has a gradient. I also want to animate the drawing of this shape by having it draw in from left to right.
Problem:
The code below works on an iPad simulator, but it doesn't work on my iPad 4 running iOS 7. Does anyone know how to make this work on the device? Is there a different way to achieve this same result?
Explanation of my Code:
My code works (only on simulator) using 3 CALayers.
gradientLayer holds my gradient.
shapeMaskLayer holds my custom shape.
animationMaskLayer animates it's path to simulate drawing from left to right
animationMaskLayer --masks--> shapeMaskLayer --masks--> gradientLayer
I then animate the frame of animationMaskLayer to animate the whole shape.
Code:
// Animation Mask Rects
CGPathRef leftStartingRectPath = CGPathCreateWithRect(CGRectMake(0, 0, 0, self.frame.size.height), 0);
CGPathRef fullViewRectPath = CGPathCreateWithRect(CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), 0);
// Animation Mask Layer
CAShapeLayer *animationMaskLayer = [CAShapeLayer layer];
animationMaskLayer.path = fullViewRectPath;
animationMaskLayer.fillColor = UIColor.blackColor.CGColor;
// Shape Mask Layer
CAShapeLayer *shapeMaskLayer = [CAShapeLayer layer];
shapeMaskLayer.path = CGPathCreateWithEllipseInRect(self.bounds, 0);
shapeMaskLayer.fillColor = [UIColor blueColor].CGColor;
shapeMaskLayer.mask = animationMaskLayer;
// Gradient Layer
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.bounds;
gradientLayer.colors = self.colors;
gradientLayer.mask = shapeMaskLayer;
mountainLayer.anchorPoint = pt(0, 0);
mountainLayer.position = pt(0, 0);
[self.layer addSublayer:gradientLayer];
// Left To Right Animation
CABasicAnimation *leftToRightAnimation = [CABasicAnimation animationWithKeyPath:#"path"];
leftToRightAnimation.duration = 0.5;
leftToRightAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
leftToRightAnimation.fromValue = (__bridge id)leftStartingRectPath;
leftToRightAnimation.toValue = (__bridge id)fullViewRectPath;
leftToRightAnimation.fillMode = kCAFillModeForwards;
leftToRightAnimation.removedOnCompletion = NO;
[animationMaskLayer addAnimation:leftToRightAnimation forKey:#"animatePath"];
// Memory Management
CGPathRelease(leftStartingRectPath);
CGPathRelease(fullViewRectPath);
Animating the mask of a mask? I'm surprised that even works in the simulator.
Have you tried nesting two layers, with the parent layer with masksToBounds on? You can then set an independent mask on both layers, and the outer layer will effectively mask your inner layer with its own mask (due to masksToBounds). It probably doesn't matter which layer gets which mask (because you want the intersection of both masks).
With the code you listed in your question, you would just need to add one line, and comment out one line to get the correct functionality:
self.layer.mask = animationMaskLayer;
// shapeMaskLayer.mask = animationMaskLayer;
I was wondering how do someone achieve the "slide to unlock effect" on a UILabel's text without using a static image as previously asked here.
// I'd like to use the uilable's current text to this sample code but not seem to be able to do it.
// ---> UIImage *textImage = [UIImage imageNamed:#"SlideToUnlock.png"];
CGFloat textWidth = textImage.size.width;
CGFloat textHeight = textImage.size.height;
CALayer *textLayer = [CALayer layer];
textLayer.contents = (id)[textImage CGImage];
textLayer.frame = CGRectMake(10.0f, 215.0f, textWidth, textHeight);
CALayer *maskLayer = [CALayer layer];
// Mask image ends with 0.15 opacity on both sides. Set the background color of the layer
// to the same value so the layer can extend the mask image.
maskLayer.backgroundColor = [[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.15f] CGColor];
maskLayer.contents = (id)[[UIImage imageNamed:#"Mask.png"] CGImage];
// Center the mask image on twice the width of the text layer, so it starts to the left
// of the text layer and moves to its right when we translate it by width.
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.frame = CGRectMake(-textWidth, 0.0f, textWidth * 2, textHeight);
// Animate the mask layer's horizontal position
CABasicAnimation *maskAnim = [CABasicAnimation animationWithKeyPath:#"position.x"];
maskAnim.byValue = [NSNumber numberWithFloat:textWidth];
maskAnim.repeatCount = 1e100f;
maskAnim.duration = 1.0f;
[maskLayer addAnimation:maskAnim forKey:#"slideAnim"];
textLayer.mask = maskLayer;
[self.view.layer addSublayer:textLayer];
Thanks
You can use the below Project. It is a UILabel with slide to unlock animation
http://code4app.net/ios/Animated-Label/505fd71a6803fa1077000001
Top: UILabel with opaque background and clear text
Clear text is rendered in drawRect: func through complicated masking process
Middle: Worker View that is performing a repeating animation moving an image behind the top label
Bottom: a UIView that you add the middle and top subview to in that order. Can be whatever color you want the text to be
An example can be seen here https://github.com/jhurray/AnimatedLabelExample