Here is the code which draws a vertical line in graph.
-(void)drawRect:(CGRect)rect
{
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
UIBezierPath *breakFastValuePath = [UIBezierPath bezierPath];
[breakFastValuePath moveToPoint:CGPointMake(89, 288)];
[breakFastValuePath addLineToPoint:CGPointMake(89,288-delegate.breakFastTotalamt)];
[breakFastValuePath closePath];
[[UIColor greenColor] setStroke];
breakFastValuePath.lineWidth = 10;
[breakFastValuePath stroke];
}
How to make the line animating from starting point to end point wen view is loaded?
You can animate the strokeEnd from 0.0 to 1.0 to give the effect that the line along its path from start to end. Look at this question (about drawing a circle in Core Animation) for reference.
I believe you need to take a different approach.
I will show you an example for a horizontal line, the vertical case will be very similar.
Use a normal UIView to represent your line with an initial frame like:
UIView *lineView = [[UIView alloc] initWithFrame:
CGRectMake(startX,startY,1,lineThickness)];//Line starts as 1 pixel long.
//Then you need to animate this inside loadView:
[UIView animateWithDuration:1//Amount of time the animation takes.
delay:0//Amount of time after which animation starts.
options: UIViewAnimationCurveEaseOut//How the animation will behave.
animations:^{
//here you can either set a CGAffineTransform, or change your view's frame.
//Both will work just fine.
lineView = CGAffineTransformMakeScale (
scaleForX,//say 100, Now the line will be a 100 pixels long.
scaleForY//say 1, Maintain line thickness.
//direction.
//Note* you could also set the frame for a similar effect.
//view's frame.
//lineView.frame = CGRectMake(startX,startY,finalLength,lineThickness)
}
completion:^(BOOL finished){//This block is called when the animation completes.
NSLog(#"Done!");
}];
Related
In my app I want to move a little UIImageView with inside a .png; this is a little insect and I want to simulate his flight. At example I want that this png do when it move an inverted eight as the infinite simbol ∞
You may use CoreAnimation. You can subclass a view, create a subview for the insect, and then assign an animation to it, following a defined path.
Your UIImageView could be animated. If it's a fly, you can do a few frames for wing moves:
NSArray *images = [NSArray arrayWithObjects:..., nil];
insect.animationImages = images;
insect.animationDuration = ??;
insect.animationRepeatCount = 0;
[insect startAnimating];
Then set an init frame for the insect:
insect.frame = CGRectMake(-120, 310, [[images objectAtIndex:0] size].width, [[images objectAtIndex:0] size].height);
And then define the path:
CGMutablePathRef aPath;
CGFloat arcTop = insect.center.y - 50;
aPath = CGPathCreateMutable();
CGPathMoveToPoint(aPath, NULL, insect.center.x, insect.center.y);
CGPathAddCurveToPoint(aPath, NULL, insect.center.x, arcTop, 240, -100, 490, 360);
CAKeyframeAnimation* arcAnimation = [CAKeyframeAnimation animationWithKeyPath: #"position"];
arcAnimation.repeatCount = HUGE_VALF;
[arcAnimation setDuration: 4.5];
[arcAnimation setAutoreverses: NO];
arcAnimation.removedOnCompletion = NO;
arcAnimation.fillMode = kCAFillModeBoth;
[arcAnimation setPath: aPath];
CFRelease(aPath);
[insect.layer addAnimation: arcAnimation forKey: #"position"];
I leave how to do the infinite loop path up to you :)
Hope it helps!
Normally, if you were to be moving things around, I'd suggest using [UIView animate...]. However, you want something to move on a complex, curvy path. So instead, I'd suggest coming up with an equation that gives the (x,y) for the insect as a function of time, and then start an NSTimer with a fairly small time interval, and every time you get an update, move the insect (perhaps using [UIView animate...]).
Another way to go is to use a 2-d animation framework such as cocos2d - then, you can get an 'update' call linked to the frame refresh rate, inside of which you update the position of your insect using the same equation as from above.
I have a mask that I've build thanks to a layer and an image. I want to move this layer with little "jumps" (without a nice translation animation).
The problem is that there is an animation happening whenever I move the frame of the mask layer. I've tried calling [maskLayer removeAllAnimations]; but it doesn't change anything.
In my code, I don't put any animation at all, so I don't understand where this animation comes from.
When I launch this function :
-(void) moveAlternativeMask:(CALayer*) maskLayer {
NSLog (#"moveAlternativeMask %#", NSStringFromCGRect(maskLayer.frame));
// Change the frame of the layer
CGRect originalFrame = maskLayer.frame;
CGFloat newYPos = maskLayer.frame.origin.y + 5.;
maskLayer.frame = CGRectMake (originalFrame.origin.x, newYPos, originalFrame.size.width, originalFrame.size.height);
// Try removing all animations on the layer
NSLog (#"Animations");
for (NSString* animationKey in maskLayer.animationKeys) {
NSLog(#" Animation With Key %#", animationKey);
}
NSLog (#"End animations");
[maskLayer removeAllAnimations];
NSLog (#"Animations after removing");
for (NSString* animationKey in maskLayer.animationKeys) {
NSLog(#" Animation With Key %#", animationKey);
}
NSLog (#"End animations after removing");
// Launch the animation with a small delay
[self performSelector:#selector(moveAlternativeMask:) withObject:maskLayer afterDelay:0.5];
}
I get this log :
----moveAlternativeMask {{0, 200}, {768, 643}} ----
Animations
Animation with key : position
End animations
Animations after removing
End animations after removing
---moveAlternativeMask {{0, 205}, {768, 643}}
Animations
Animation with key : position
End animations
Animations after removing
End animations after removing
---moveAlternativeMask {{0, 210}, {768, 643}}
Animations
Animation with key : position
End animations
Animations after removing
End animations after removing
So the layer is moved as I want, the only problem is that the "position" animation keep coming back...
Note that the CALayer was build like this :
UIImage *maskImage = [UIImage imageNamed:#"masque2.png"];
self.maskLayer = [CALayer layer];
self.maskLayer.contents = (id) maskImage.CGImage;
self.maskLayer.frame = CGRectMake(0, ORIGINAL_Y_POSITION, 768, 643);
[myView.layer setMask:self.maskLayer];
Hmm, very strange. I have 2 ideas:
Is it feasible to try doing this with a UIView instead?
Could you perhaps try removeFromSuperview and addSubview instead of just changing the frame?
My goal is simple; I want to create a program that displays an UIImage, and when swiped from bottom to top, displays another UIImage. The images here could be a happy face/sad face. The sad face should be the starting point, the happy face the end point. When swiping your finger the part below the finger should be showing the happy face.
So far I tried solving this with the frame and bounds properties of the UIImageview I used for the happy face image.
What this piece of code does is wrong, because the transition starts in the center of the screen and not the bottom. Notice that the origin of both frame and bounds are at 0,0...
I have read numerous pages about frames and bounds, but I don't get it. Any help is appreciated!
The loadimages is called only once.
- (void)loadImages {
sadface = [UIImage imageNamed:#"face-sad.jpg"];
happyface = [UIImage imageNamed:#"face-happy.jpg"];
UIImageView *face1view = [[UIImageView alloc]init];
face1view.image = sadface;
[self.view addSubview:face1view];
CGRect frame;
CGRect contentRect = self.view.frame;
frame = CGRectMake(0, 0, contentRect.size.width, contentRect.size.height);
face1view.frame = frame;
face2view = [[UIImageView alloc]init];
face2view.layer.masksToBounds = YES;
face2view.contentMode = UIViewContentModeScaleAspectFill;
face2view.image = happyface;
[self.view addSubview:face2view];
frame = CGRectMake(startpoint.x, 0, contentRect.size.width, contentRect.size.height);
face2view.frame = frame;
face2view.clipsToBounds = YES;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint movepoint = [[touches anyObject] locationInView: self.view];
NSLog(#"movepoint: %f %f", movepoint.x, movepoint.y);
face2view.bounds = CGRectMake(0, 0, 320, 480 - movepoint.y);
}
The UIImages and UIImageViews are properly disposed of in the dealloc function.
Indeed, you seem to be confused about frames and bounds. In fact, they are easy. Always remember that any view has its own coordinate system. The frame, center and transform properties are expressed in superview's coordinates, while the bounds is expressed in the view's own coordinate system. If a view doesn't have a superview (not installed into a view hierarchy yet), it still has a frame. In iOS the frame property is calculated from the view's bounds, center and transform. You may ask what the hell frame and center mean when there's no superview. They are used when you add the view to another view, allowing to position the view before it's actually visible.
The most common example when a view's bounds differ from its frame is when it is not in the upper left corner of its superview: its bounds.origin may be CGPointZero, while its frame.origin is not. Another classic example is UIScrollView, which frequently modifies its bounds.origin to make subviews scroll (in fact, modifying the origin of the coordinate system automatically moves every subview without affecting their frames), while its own frame is constant.
Back to your code. First of all, when you already have images to display in image views, it makes sense to init the views with their images:
UIImageView *face1view = [[UIImageView alloc] initWithImage: sadface];
That helps the image view to immediately size itself properly. It is not recommended to init views with -init because that might skip some important code in their designated initializer, -initWithFrame:.
Since you add face1view to self.view, you should really use its bounds rather than its frame:
face1view.frame = self.view.bounds;
Same goes for the happier face. Then in -touchesMoved:… you should either change face2view's frame to move it inside self.view or (if self.view does not contain any other subviews besides faces) modify self.view's bounds to move both faces inside it together. Instead, you do something weird like vertically stretching the happy face inside face2view. If you want the happy face to slide from the bottom of self.view, you should initially set its frame like this (not visible initially):
face2view.frame = CGRectOffset(face2view.frame, 0, CGRectGetHeight(self.view.bounds));
If you choose to swap faces by changing image views' frames (contrasted with changing self.view's bounds), I guess you might want to change both the views' frame origins, so that the sad face slides up out and the happy face slides up in. Alternatively, if you want the happy face to cover the sad one:
face2view.frame = face1view.frame;
Your problem seems to have something to do with the face2view.bounds in touchesMoved.
You are setting the bounds of this view to the rect, x:0, y:0, width:320, height:480 - y
x = 0 == left on the x axis
y = 0 == top on the y axis
So you are putting this image frame at the upper left corner, and making it fill the whole view. That's not what you want. The image simply becomes centered in this imageView.
I have a UIView subclass which uses a CAShapeLayer mask on its CALayer. The mask uses a distinct shape, with three rounded corners and a cut out rectangle in the remaining corner.
When I resize my UIView using a standard animation block, the UIView itself and its CALayer resize just fine. The mask, however, is applied instantly, which leads to some drawing issues.
I've tried animating the mask's resizing using a CABasicAnimation but didn't have any luck getting the resizing animated.
Can I somehow achieve an animated resizing effect on the mask? Do I need to get rid of the mask, or will I have to change something about the way I currently draw the mask (using - (void)drawInContext:(CGContextRef)ctx).
Cheers,
Alex
I found the solution to this problem. Other answers are partially correct and are helpful.
The following points are important to understanding the solution:
The mask property is not animatable itself.
Since the mask is a CALayer it can be animated on its own.
Frame is not animatable, use bounds and position. This may not apply to you(if you weren't trying to animate the frame), but was an issue for me. (See Apple QA 1620)
A view layer's mask is not tied to UIView so it will not receive the core animation transaction that is applied to the view's layer.
We are modifying the CALayer directly, so we can't expect that UIView will have any idea of what we are trying to do, so the UIView animation won't create the core animation transaction to include changes to our properties.
In order to solve, we are going to have to tap into Core Animation ourselves, and can't rely on the UIView animation block to do the work for us.
Simply create a CATransaction with the same duration that you are using with [UIView animateWithDuration:...]. This will create a separate animation, but if your durations and easing function is the same, it should animate exactly with the other animations in your animation block.
NSTimeInterval duration = 0.5;// match this to the value of the UIView animateWithDuration: call
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:duration] forKey:kCATransactionAnimationDuration];
self.myView.layer.mask.position = CGPointMake(newX, 0);
self.myView.layer.mask.bounds = CGRectMake(0, 0, newWidth, newHeight);
[CATransaction commit];
I use a CAShapeLayer to mask a UIView by setting self.layer.mask to that shape layer.
To animate the mask whenever the size of the view changes I overwrote the -setBounds: to animate the mask layer path if the bounds are changed during an animation.
Here's how I implemented it:
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
CAPropertyAnimation *boundsAnimation = (CABasicAnimation *)[self.layer animationForKey:#"bounds"];
// update the mask
self.maskLayer.frame = self.layer.bounds;
// if the bounds change happens within an animation, also animate the mask path
if (!boundsAnimation) {
self.maskLayer.path = [self createMaskPath];
} else {
// copying the original animation allows us to keep all animation settings
CABasicAnimation *animation = [boundsAnimation copy];
animation.keyPath = #"path";
CGPathRef newPath = [self createMaskPath];
animation.fromValue = (id)self.maskLayer.path;
animation.toValue = (__bridge id)newPath;
self.maskLayer.path = newPath;
[self.maskLayer addAnimation:animation forKey:#"path"];
}
}
(For the example self.maskLayer is set to `self.layer.mask)
My -createMaskPath calculates the CGPathRef that I use to mask the view. I also update the mask path in -layoutSubviews.
The mask property of CALayer is not animatable which explains your lack of luck in that direction.
Does the drawing of your mask depend on the frame/bounds of the mask? (Can you provide some code?) Does the mask have needsDisplayOnBoundsChange property set?
Cheers,
Corin
To animate the bounds change of the mask layer of a UIView: subclass UIView, and animate the mask with a CATransaction - similar to Kekodas answer but more general:
#implementation UIMaskView
- (void) layoutSubviews {
[super layoutSubviews];
CAAnimation* animation = [self.layer animationForKey:#"bounds"];
if (animation) {
[CATransaction begin];
[CATransaction setAnimationDuration:animation.duration];
}
self.layer.mask.bounds = self.layer.bounds;
if (animation) {
[CATransaction commit];
}
}
#end
The mask parameter doesn't animate, but you can animate the layer which is set as the mask...
If you animate the CAShapeLayer's Path property, that should animate the mask. I can verify that this works from my own projects. Not sure about using a non-vector mask though. Have you tried animating the contents property of the mask?
Thanks,
Jon
I couldn't find any programmatical solution so I just draw an png image with correct shape and alpha values and used that instead. That way I don't need to use a mask...
It is possible to animate the mask change.
I prefer to use CAShapeLayer as the mask layer. It is very convenient to animate a mask change with the help of property path.
Before animate any change, dump the content of the source into an instance CGImageRef, and create a new layer for animation. Hide the original layer during the animation and show it when animation ends.
The following is a sample code for creating key animation on property path.
If you want to create your own path animation, make sure that there are always same number of points in the paths.
- (CALayer*)_mosaicMergeLayer:(CGRect)bounds content:(CGImageRef)content isUp:(BOOL)isUp {
CALayer* layer = [CALayer layer];
layer.frame = bounds;
layer.backgroundColor = [[UIColor clearColor] CGColor];
layer.contents = (id)content;
CAShapeLayer* maskLayer = [CAShapeLayer layer];
maskLayer.fillColor = [[UIColor blackColor] CGColor];
maskLayer.frame = bounds;
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.path = ( isUp ? [self _maskArrowUp:-bounds.size.height*2] : [self _maskArrowDown:bounds.size.height*2] );
layer.mask = maskLayer;
CAKeyframeAnimation* ani = [CAKeyframeAnimation animationWithKeyPath:#"path"];
ani.removedOnCompletion = YES;
ani.duration = 0.3f;
ani.fillMode = kCAFillModeForwards;
ani.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
NSArray* values = ( isUp ?
[NSArray arrayWithObjects:
(id)[self _maskArrowUp:0],
(id)[self _maskArrowUp:-ceilf(bounds.size.height*1.2)],
nil]
:
[NSArray arrayWithObjects:
(id)[self _maskArrowDown:0],
(id)[self _maskArrowDown:bounds.size.height],
nil]
);
ani.values = values;
ani.delegate = self;
[maskLayer addAnimation:ani forKey:nil];
return layer;
}
- (void)_startMosaicMergeAni:(BOOL)up {
CALayer* overlayer = self.aniLayer;
CGRect bounds = overlayer.bounds;
self.firstHalfAni = NO;
CALayer* frontLayer = nil;
frontLayer = [self _mosaicMergeLayer:bounds
content:self.toViewSnapshot
isUp:up];
overlayer.contents = (id)self.fromViewSnapshot;
[overlayer addSublayer:frontLayer];
}
Swift 3+ answer based on Kekoa's solution:
let duration = 0.15 //match this to the value of the UIView.animate(withDuration:) call
CATransaction.begin()
CATransaction.setValue(duration, forKey: kCATransactionAnimationDuration)
myView.layer.mask.position = CGPoint(x: [new X], y: [new Y]) //just an example
CATransaction.commit()
Swift implementation of #stigi answer, my mask layer is called shape Layer
override var bounds: CGRect {
didSet {
// debugPrint(self.layer.animationKeys()) //Very useful for know if animation is happening and key name
let propertyAnimation = self.layer.animation(forKey: "bounds.size")
self.shapeLayer?.frame = self.layer.bounds
// if the bounds change happens within an animation, also animate the mask path
if let boundAnimation = propertyAnimation as? CABasicAnimation {
// copying the original animation allows us to keep all animation settings
if let basicAnimation = boundAnimation.copy() as? CABasicAnimation {
basicAnimation.keyPath = "path"
let newPath = UIBezierPathUtils.customShapePath(rect: self.layer.bounds, cornersTriangleSize: cornerTriangleSize).cgPath
basicAnimation.fromValue = self.shapeLayer?.path
basicAnimation.toValue = newPath
self.shapeLayer?.path = newPath
self.shapeLayer?.add(basicAnimation, forKey: "path")
}
} else {
self.shapeLayer?.path = UIBezierPathUtils.customShapePath(rect: self.layer.bounds, cornersTriangleSize: cornerTriangleSize).cgPath
}
}
}
i am trying to implement a graph which a uiview draws. There are 3 UIViews to animate a left/right slide. The problem is, that i can't cancel the UIView animation. So I replaced the UIViews by CALayer. Now, the question is if CALayer is appicable for this? Is it normal to draw on a CALayer like this? And why is a CALayer so slow when I manipulate the frame properties.
CGRect frame = curve.frame;
frame.origin.x -= diffX;
[curve setFrame:frame];
Is there a alternativ?
P.S. I am a german guy. Sorry for mistakes.
I got the animation with CATransaction, but now I will animate a x move with CABasicAnimation. That's no problem expect that the position of the layer go back to the previous x.
CABasicAnimation *theAnimation;
theAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
theAnimation.delegate = self;
theAnimation.duration = 1.0;
theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
CGPoint position = [[curves objectAtIndex:i]position];
position.x = [[curves objectAtIndex:i]position].x - diffX;
[theAnimation setToValue:[NSValue valueWithCGPoint:position]];
[[curves objectAtIndex:i] addAnimation:theAnimation forKey:[NSString stringWithFormat: #"translate.x.%d", index]];
The position changes the position (e.g. xStart = 100, xEnd = 200), but when the animation ends the layer goes back to the beginning x (e.g. x = 100).
Is that normal? How can I solve this problem, that the end position doesn't change anymore?
I tried to changed the removeOnComplete property but that didn't effect.
Hope for help.
Markus
Not sure what you mean by 'slow', but setting the frame of a CALayer in this way uses 'implicit animation'. That is, it will animated the transition from the old frame to the new frame. You can turn this off:
[CATransaction begin];
[CATransaction setValue: (id) kCFBooleanTrue forKey: kCATransactionDisableActions];
[curve setFrame:frame];
[CATransaction commit];
However, this is usually considered an advantage of CALayer. You way want to just use UIViews here, which will not, by default, animate transitions such as this.
Instead of setting the destination position in theAnimation, just set the position property of the thing you want to move.
When you addAnimation:theAnimation, you're setting the "visual style" of any changes to the keyPath property you specified.
When you change the position of the object that the animation is attached to, say from (0,0) to (500,500), CoreAnimation will animate the change for you. The theAnimation object doesn't need the start and end positions, since the underlying object has them.