I just updated to Xcode 8 and I had problem with Blur (UIVisualEffectView) on iOS 10, but I solve it. But after I fixed the blur layer mask second problem is appears, when I getting the reference of maskView from UIVisualEffectView and apply the new UIBezierPath nothing happens.
Code example:
-(void)prepareEffectView{
//Frame
CGRect frame = self.view.bounds;
UIView *maskView = [[UIView alloc] initWithFrame:frame];
maskView.backgroundColor = [UIColor blackColor];
maskView.layer.mask = ({ // This mask draws a rectangle and crops a circle inside it.
UIBezierPath *maskLayerPath = [UIBezierPath bezierPath];
[maskLayerPath appendPath:[UIBezierPath bezierPathWithRect:frame]];
[maskLayerPath appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(self.btnScan.center.x, self.btnScan.center.y, 0, 0)]];
[maskLayer setPath:maskLayerPath.CGPath];
[maskLayerPath setUsesEvenOddFillRule:YES];
CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = maskLayerPath.CGPath;
mask.fillRule = kCAFillRuleEvenOdd;
mask;
});
self.visualEffectView.maskView = maskView;
}
-(void)openAperture{
//Blur layer
CGRect frame = self.view.bounds;
CAShapeLayer *maskLayer = (CAShapeLayer *)self.visualEffectView.maskView.layer.mask;
//New path value
UIBezierPath *maskLayerPath = [UIBezierPath bezierPath];
[maskLayerPath appendPath:[UIBezierPath bezierPathWithRect:frame]];
[maskLayerPath appendPath:[UIBezierPath bezierPathWithRoundedRect:UIEdgeInsetsInsetRect(frame, UIEdgeInsetsMake(58, 15, 66, 15)) cornerRadius:1]];
[maskLayer setPath:maskLayerPath.CGPath];
//Animation
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.delegate = self;
animation.duration = 0.44;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = (id)maskLayer.path;
animation.toValue = (id)maskLayerPath.CGPath;
[maskLayer addAnimation:animation forKey:#"animatePath"];
}
Apple Note About VisualEffectView issue on iOS 10
Transitions are tough in this realm, as maskView is the only way to
ensure that the effect works, but maskView also isn't animatable.
https://forums.developer.apple.com/message/184810#184810
The only the way to use "CAShapeLayer" as a mask is to apply your shape to UIView.layer.mask and then put it to UIVisualEffectView.maskView.
The maskView of UIVisualEffectView isn't animatable.
I have the same problem.
I tried to do next: (Swift code)
visualEffectView.layer.mask = maskView.layer.mask
instead of visualEffectView.mask = maskView
If so, path animation works fine, but then blur effect is gone.
If you find a solution, please update the post.
Related
I'm trying to add an overlay to my video which contains a sublayer that has the content changed over time. So I'm adding CAKeyframeAnimation to the layer as follows:
CALayer *overlayLayer = [CALayer layer];
[overlayLayer setFrame:CGRectMake(0, 0, size.width, size.height)];
[overlayLayer setMasksToBounds:YES];
NSArray *frames = [self.gifImage frames];
CALayer *aLayer = [CALayer layer];
UIImage *firstImage = [frames objectAtIndex:0];
CGFloat nativeWidth = CGImageGetWidth(firstImage.CGImage);
CGFloat nativeHeight = CGImageGetHeight(firstImage.CGImage);
CGRect frame = CGRectMake(0.0, 0.0, nativeWidth, nativeHeight);
aLayer.frame = frame;
aLayer.contents = (id)firstImage.CGImage;
[aLayer setMasksToBounds:YES];
CAKeyframeAnimation *animationSequence = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
animationSequence.calculationMode = kCAAnimationDiscrete;
animationSequence.duration = [self.gifImage totalDuration];
animationSequence.repeatCount = HUGE_VALF;
NSMutableArray *animationSequenceArray = [[NSMutableArray alloc] init];
for (UIImage *image in frames) {
[animationSequenceArray addObject:(id)image.CGImage];
}
animationSequence.values = animationSequenceArray;
[aLayer addAnimation:animationSequence forKey:#"contents"];
[overlayLayer addSublayer:aLayer];
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.frame = CGRectMake(0, 0, size.width, size.height);
videoLayer.frame = CGRectMake(0, 0, size.width, size.height);
[parentLayer addSublayer:videoLayer];
[parentLayer addSublayer:overlayLayer];
composition.animationTool = [AVVideoCompositionCoreAnimationTool
videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
Somehow when I add aLayer as a sublayer of my view, it animates properly. But When I add it to the overlay of the video, the animation doesn't work. Is there something I'm missing here? Any help is appreciated.
If you use kCAAnimationDiscrete, here's a checklist:
keyTimes needs a value from 0-1, not a time
keyTimes must start at 0.0 and end with value 1.0
keyTimes must have one more value than your animation frames count
I've finally managed to fix this issue by changing calculationMode to kCAAnimationLinear. This seems to be a silly issue, but I hope it may be useful for someone.
I'm trying to add an animation below the annotations of my MKMapView but with no luck.
I tryed to change the CAShapeLayer zPosition but the circle end up below the map.
Here is my current code to add the animation to the map:
int radius = 55;
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor blackColor].CGColor;
circle.lineWidth = radius*2;
circle.opacity = 0.5;
[self.mapView.layer addSublayer:circle];
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 2.5; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 100.0; // Animate only once..
drawAnimation.removedOnCompletion = YES; // Remain stroked after the animation..
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:1.0f];
functionWithName:kCAMediaTimingFunctionEaseIn];
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
After 3 days without an answer I decided to remove my annotation from the mkview and put the animation and the image corresponding to the annotation in another view, above the map.
I am drawing a circle, with an initial radius of 200
self.circle = [CAShapeLayer layer];
self.circle.fillColor = nil;
self.circle.strokeColor = [UIColor blackColor].CGColor;
self.circle.lineWidth = 7;
self.circle.bounds = CGRectMake(0, 0, 2 * radius, 2 * radius);
self.circle.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.radius, self.radius)
Can anyone please tell me how to animate a change to a radius of 100?
This is how I ended up doing it:
UIBezierPath *newPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(newRadius, newRadius) radius:newRadius startAngle:(-M_PI/2) endAngle:(3*M_PI/2) clockwise:YES];
CGRect newBounds = CGRectMake(0, 0, 2 * newRadius, 2 * newRadius);
CABasicAnimation* pathAnim = [CABasicAnimation animationWithKeyPath: #"path"];
pathAnim.toValue = (id)newPath.CGPath;
CABasicAnimation* boundsAnim = [CABasicAnimation animationWithKeyPath: #"bounds"];
boundsAnim.toValue = [NSValue valueWithCGRect:newBounds];
CAAnimationGroup *anims = [CAAnimationGroup animation];
anims.animations = [NSArray arrayWithObjects:pathAnim, boundsAnim, nil];
anims.removedOnCompletion = NO;
anims.duration = 2.0f;
anims.fillMode = kCAFillModeForwards;
[self.circle addAnimation:anims forKey:nil];
This is, I believe, the simpler and preferred way to do it:
UIBezierPath *newPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(newRadius, newRadius) radius:newRadius startAngle:(-M_PI/2) endAngle:(3*M_PI/2) clockwise:YES];
CABasicAnimation* pathAnim = [CABasicAnimation animationWithKeyPath:#"path"];
pathAnim.fromValue = (id)self.circle.path;
pathAnim.toValue = (id)newPath.CGPath;
pathAnim.duration = 2.0f;
[self.circle addAnimation:pathAnim forKey:#"animateRadius"];
self.circle.path = newPath.CGPath;
I got this approach from the Apple Documentation here (See listing 3-2). The important things here are that you're setting the fromValue and also setting the final value for self.circle's path to newPath.CGPath right after you set up the animation. An animation does not change the underlying value of the model, so in the other approach, you're not actually making a permanent change to the shape layer's path variable.
I've used a solution like the chosen answer in the past (using removedOnCompletion and fillMode etc) but I found that on certain versions of iOS they caused memory leaks, and also sometimes that approach just doesn't work for some reason.
With this approach, you're creating the animation explicitly (both where it starts from and where it ends), and you're specifying the final permanent value for the path at the end (with that last line), so that there's no need to mess with not removing the animation when it finishes (which I believe is what leads to memory leaks if you do this animation a lot of times... the unremoved animations build up in memory).
Also, I removed the animation of the bounds because you don't need it to animate the circle. Even if the path is drawn outside the bounds of the layer, it'll still be fully shown (see the docs for CAShapeLayer about that). If you do need to animate the bounds for some other reason, you can use the same approach (making sure to specify the fromValue and the final value for bounds).
You can use a CAAnimationGroup to animate the bounds and path of your CAShapeLayer:
- (void)animateToRadius:(CGFloat)newRadius {
CAShapeLayer *shapeLayer = ...;
UIBezierPath *newBezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(newRadius, newRadius)];
NSValue *newBounds = [NSValue valueWithCGRect:CGRectMake(0,0,2*newRadius,2*newRadius);
CAAnimationGroup *group = [CAAnimationGroup animation];
NSMutableArray *animations = [#[] mutableCopy];
NSDictionary *animationData = #{#"path":newBezierPath.CGPath, #"bounds":newBounds};
for(NSString *keyPath in animationData) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath];
animation.toValue = animationData[keyPath];
[animations addObject:animation];
}
group.animations = [animations copy];
group.duration = 2;
group.removedOnCompletion = NO;
group.fillMode = kCAFillModeForwards;
[shapeLayer addAnimation:group forKey:nil];
}
Here is a direct swift conversion of Jonathan's code should anyone need it.
func scaleShape(shape : CAShapeLayer){
let newRadius : CGFloat = 50;
let newPath: UIBezierPath = UIBezierPath(arcCenter: CGPointMake(newRadius, newRadius), radius: newRadius, startAngle: (CGFloat(-M_PI) / 2.0), endAngle: (3 * CGFloat(M_PI) / 2), clockwise: true);
let newBounds : CGRect = CGRect(x: 0, y: 0, width: 2 * newRadius, height: 2 * newRadius);
let pathAnim : CABasicAnimation = CABasicAnimation(keyPath: "path");
pathAnim.toValue = newPath.CGPath;
let boundsAnim : CABasicAnimation = CABasicAnimation(keyPath: "bounds");
boundsAnim.toValue = NSValue(CGRect: newBounds);
let anims: CAAnimationGroup = CAAnimationGroup();
anims.animations = [pathAnim, boundsAnim];
anims.removedOnCompletion = false;
anims.duration = 2.0;
anims.fillMode = kCAFillModeForwards;
shape.addAnimation(anims, forKey: nil);
}
I've been unsuccessful at animating a flashing stroke on a CAShapeLayer using the answer from this previous thread, and after many searches I can find no other examples of animating the stroke using CABasicAnimation.
What I want to do is have the stroke of my CAShapeLayer pulse between two colors. Using CABasicAnimation for opacity works fine, but the [CABasicAnimation animationWithKeyPath:#"strokeColor"] eludes me, and I'd appreciate any advice on how to successfully implement.
CABasicAnimation *strokeAnim = [CABasicAnimation animationWithKeyPath:#"strokeColor"];
strokeAnim.fromValue = (id) [UIColor blackColor].CGColor;
strokeAnim.toValue = (id) [UIColor purpleColor].CGColor;
strokeAnim.duration = 1.0;
strokeAnim.repeatCount = 0.0;
strokeAnim.autoreverses = NO;
[shapeLayer addAnimation:strokeAnim forKey:#"animateStrokeColor"];
// CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
// opacityAnimation.fromValue = [NSNumber numberWithFloat:0.0];
// opacityAnimation.toValue = [NSNumber numberWithFloat:1.0];
// opacityAnimation.duration = 1.0;
// opacityAnimation.repeatCount = 0.0;
// opacityAnimation.autoreverses = NO;
// [shapeLayer addAnimation:opacityAnimation forKey:#"animateOpacity"];
Uncommenting the opacity animation results in an expected opacity fade. The stroke animation produces no effect. An implicit strokeColor change animates as expected, but I would like documented confirmation that strokeColor can be explicitly animated using CABasicAnimation.
Update: The specific problem was that shapeLayer.path was NULL. Correcting that fixed the problem.
The code below works great for me. What is the lineWidth of your shapeLayer stroke path? Could that be the issue?
- (void)viewDidLoad
{
[super viewDidLoad];
UIBezierPath * circle = [UIBezierPath bezierPathWithOvalInRect:self.view.bounds];
CAShapeLayer * shapeLayer = [CAShapeLayer layer];
shapeLayer.path = circle.CGPath;
shapeLayer.strokeColor =[UIColor blueColor].CGColor;
[shapeLayer setLineWidth:15.0];
[self.view.layer addSublayer:shapeLayer];
CABasicAnimation *strokeAnim = [CABasicAnimation animationWithKeyPath:#"strokeColor"];
strokeAnim.fromValue = (id) [UIColor redColor].CGColor;
strokeAnim.toValue = (id) [UIColor greenColor].CGColor;
strokeAnim.duration = 3.0;
strokeAnim.repeatCount = 0;
strokeAnim.autoreverses = YES;
[shapeLayer addAnimation:strokeAnim forKey:#"animateStrokeColor"];
}
Let me know if it works for you...
I'm trying to animate the appearance of a segment of a circle. To archive this I use a CABasicAnimations which works quiet fine.
The animation starts on top and moves quiet nicely to one third of the whole circle. But when the animation finishes, the circle is being drawn completely immediately.
How can I prevent that?
Here is the source code of my custom UIView:
- (void)drawRect:(CGRect)rect
{
int radius = 100;
int strokeWidth = 10;
CGColorRef color = [UIColor redColor].CGColor;
int timeInSeconds = 5;
CGFloat startAngle = 0;
CGFloat endAngle = 0.33;
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius) cornerRadius:radius].CGPath;
circle.position = CGPointMake(CGRectGetMidX(self.frame)-radius, CGRectGetMidY(self.frame)-radius);
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = color;
circle.lineWidth = strokeWidth;
[self.layer addSublayer:circle];
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = timeInSeconds;
drawAnimation.repeatCount = 1.0;
drawAnimation.removedOnCompletion = NO;
drawAnimation.fromValue = [NSNumber numberWithFloat:startAngle];
drawAnimation.toValue = [NSNumber numberWithFloat:endAngle];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
}
When you apply an animation to a layer, Core Animation creates a copy of the layer and animates the properties of the copy. The original layer is called the model layer and the copy is called the presentation layer. An animation never changes the properties of the model layer.
You tried to fix this by setting removedOnCompletion to NO. You would also have to set the fillMode of the animation to make this work, but it's not really the correct way to animate a property.
The correct way is to change the property on your model layer, then apply the animation.
// Change the model layer's property first.
circle.strokeEnd = endAngle;
// Then apply the animation.
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = timeInSeconds;
drawAnimation.fromValue = [NSNumber numberWithFloat:startAngle];
drawAnimation.toValue = [NSNumber numberWithFloat:endAngle];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
This is explained in the Core Animation Essentials video from WWDC 2011. I highly recommend watching it.