I wanted to make a circular Slider which is draggable and animatable. So far I've managed to build the slider and use the drag handle to move it around and even animate it. Sometimes animation goes wrong (wrong direction or shortest direction. I've subclassed a UIView (Will be a UIControl soon, just wanted to get the animation right first) added a PanGestureRecognizer and several layers for the drawing.
So how do I fix this weird behaviour? I've someone could help me here, I'd be thankful :)
Here's the sample project -> http://cl.ly/2l0O3b1I3U0X
Thanks a lot!
EDIT:
Here's the drawing code:
CALayer *aLayer = [CALayer layer];
aLayer.bounds = CGRectMake(0, 0, 170, 170);
aLayer.position = self.center;
aLayer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
self.handleHostLayer = [CALayer layer];
self.handleHostLayer.bounds = CGRectMake(0, 0, 170, 170);
self.handleHostLayer.position = CGPointMake(CGRectGetMaxX(aLayer.bounds) - 170/2.0, CGRectGetMaxY(aLayer.bounds) - 170/2.0);
[aLayer addSublayer:self.handleHostLayer];
[self.layer addSublayer:aLayer];
self.handle = [CALayer layer];
self.handle.bounds = CGRectMake(0, 0, 50, 50);
self.handle.cornerRadius = 25;
self.handle.backgroundColor = [UIColor whiteColor].CGColor;
self.handle.masksToBounds = NO;
self.handle.shadowOffset = CGSizeMake(3.0, 0.0);
self.handle.shadowRadius = 0;
self.handle.shadowOpacity = .15;
self.handle.shadowColor = [UIColor blackColor].CGColor;
[self.handleHostLayer addSublayer:self.self.handle];
Here's the animation code:
CGFloat handleTarget = ToRad(DEG);
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotationAnimation.fromValue = #([[self.handleHostLayer valueForKeyPath:#"transform.rotation"] floatValue]);
rotationAnimation.toValue = #(handleTarget);
rotationAnimation.duration = .5;
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.fillMode = kCAFillModeForwards;
rotationAnimation.cumulative = YES;
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[self.handleHostLayer addAnimation:rotationAnimation forKey:#"transform.rotation"];
OK, I looked at your project. Your problem is that the to and from angles don't both fall in the 0≤ɸ<2π range.
You can make sure that they do by adding and removing 2π until they both are within that range.
CGFloat fromAngle = [[self.handleHostLayer valueForKeyPath:#"transform.rotation"] floatValue];
CGFloat toAngle = handleTarget;
while (fromAngle >= 2.0*M_PI) { fromAngle -= 2*M_PI; }
while (toAngle >= 2.0*M_PI) { toAngle -= 2*M_PI; }
while (fromAngle < 0.0) { fromAngle += 2*M_PI; }
while (toAngle < 0.0) { toAngle += 2*M_PI; }
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotationAnimation.fromValue = #(fromAngle);
rotationAnimation.toValue = #(toAngle);
// As before...
Another things you could change is the misuse of removeOnCompletion. I did a long explanation in this answer about why it's bad to do so.
In short: you are not animating from the value that you think you are animating since you inadvertently introduce a difference between the value of the layers property and what you see on screen.
Skip that line all together. You can also skip the skip the cumulative line since you are not repeating the animation. Now, if your animations doesn't stick: set the model value to its final value before adding the animation to it.
Related
I have this code to draw lines on image user takes. I have successfully added feature to draw what ever the user feel like drawing on his/her image. But the lines which i draw is not smooth enough. I have tried the answers stack overflow experts suggested to others. But mine still looks rough. And one speciality is that I use pan gesture instead of touch, will that make any problems? Expert advices and codes are needed. Thanks in advance. Happy Coding. !
And this is from where I call the drawlineFrom method:
-(void)drawLineFrom:(CGPoint)from endPoint:(CGPoint)to
{
counter++;
bezierPath = UIBezierPath.bezierPath;
NSLog(#"UIGestureRecognizerStateChanged here");
[bezierPath moveToPoint: CGPointMake(from.x, from.y)];
[bezierPath addLineToPoint:CGPointMake(to.x, to.y)];
[bezierPath closePath];
bezierPath.usesEvenOddFillRule = YES;
box = [CAShapeLayer layer];
box.frame = CGRectMake(0, 0, 500, 500);
box.path = bezierPath.CGPath;
box.strokeColor = [UIColor colorWithRed:0.992 green:0.322 blue:0.212 alpha:1.00].CGColor;
box.fillColor = [UIColor colorWithRed:0.992 green:0.322 blue:0.212 alpha:1.00].CGColor;
box.lineWidth = 5;
[self.myImage.layer addSublayer:box ];
[layerarray addObject:box];
NSLog(#"AAAAARRRRTTTT%#",layerarray);
}
{
startPoint = [gesture locationInView:self.selfieImage];
if(gesture.state == UIGestureRecognizerStateChanged)
{
NSLog(#"Touched here \n :X-%f \n Y-%f",[gesture locationInView:self.selfieImage].x,[gesture locationInView:self.selfieImage].y);
startPoint = lastPoint;
lastPoint = [gesture locationInView:self.selfieImage];
[self drawLineFrom:startPoint endPoint:lastPoint];
}
Here is my code:
// setting background
_ground = [[SKSpriteNode alloc]
initWithColor:[SKColor greenColor]
size:CGSizeMake([Helpers landscapeScreenSize].width, 50)];
_ground.name = #"ground";
_ground.zPosition = 1;
_ground.anchorPoint = CGPointMake(0, 0);
_ground.position = CGPointMake(0, 0);
_ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake([Helpers landscapeScreenSize].width, 50)];
_ground.physicsBody.dynamic = NO;
_ground.physicsBody.affectedByGravity = NO;
_ground.physicsBody.categoryBitMask = groundCategory;
_ground.physicsBody.collisionBitMask = elementCategory;
_ground.physicsBody.contactTestBitMask = elementCategory;
Green rectangle positioning is ok, but SKPhysicsBody is not representing this rectangle correctly. It looks like SKPhysicsBody is not "moving" its body according to sprite position and anchorPoint. SKPhysicsBody is moved left for _ground.width points.
What am I doing wrong?
PS.
Changing to this (code) solved my problem, but I really dont like this solution, it seems an ugly way to position an element.
_ground.position = CGPointMake([Helpers landscapeScreenSize].width / 2, 0);
+removing:
_ground.position = CGPointMake(0, 0);
I had exactly the same issue yesterday ;)
For me I solved this by using the default sprite positioning by SpriteKit. I just set the position and leave the anchor point at it's default. Then you set the physicsbody to the size of the sprite.
Change your code to the following and check if it works:
_ground = [[SKSpriteNode alloc]
initWithColor:[SKColor greenColor]
size:CGSizeMake([Helpers landscapeScreenSize].width, 50)];
_ground.position = CGPointMake([Helpers landscapeScreenSize].width / 2, 0);
_ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_ground.size];
If you still have no luck with this you can also try to use another initialisation for your sprite (this works in my project). Try it with a texture:
[[SKSpriteNode alloc] initWithTexture:[SKTexture textureWithCGImage:[UIImage imageWithColor:[UIColor whiteColor]].CGImage] color:[UIColor whiteColor] size:CGSizeMake(PLAYER_WIDTH, PLAYER_HEIGHT)];
don't forget to implement the (UIImage*)imageWithColor: method to create a 1x1 pixel image of your desired color for you (see here).
I am working on a circular progress bar that i've created using UIBezierPath. The progress bar looks like the following picture:
My question is: how to make the edge of the arc rounded and not rectangular?
The code i used to draw the arc looks like is:
// Draw the arc with bezier path
int radius = 100;
arc = [CAShapeLayer layer];
arc.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 50) radius:radius startAngle:M_PI endAngle:M_PI/150 clockwise:YES].CGPath;
arc.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
arc.fillColor = [UIColor clearColor].CGColor;
arc.strokeColor = [UIColor purpleColor].CGColor;
arc.cornerRadius = 0.5;
arc.lineWidth = 6;
[self.view.layer addSublayer:arc];
// Animation of the progress bar
drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 5.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
drawAnimation.removedOnCompletion = YES; // Remain stroked after the animation..
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:10.0f];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[arc addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
I tried to use arc.cornerRadius but it seemed to return nothing.
Any help would be appreciated.
Thank you in advance!
Granit
Set lineCapStyle to kCGLineCapRound (On the bezier path) to draw the ends of the lines with a round edge.
You can also set the lineCap on the shape layer to do the same thing.
To make corner as round you can use this.
arc.lineCap = #"round";
The swift equivalent is:
let circlePath = UIBezierPath(…)
circlePath.lineCapStyle = .round
When I put shadow etc. with CALayer my app is lagging when I double tap on the home button to see tasks running. I don't have any other lag, just when I double tap.
I call this method 20 times to put 20 images :
- (UIView *)createImage:(CGFloat)posX posY:(CGFloat)posY imgName:(NSString *)imgName
{
UIView *myView = [[UIView alloc] init];
CALayer *sublayer = [CALayer layer];
sublayer.backgroundColor = [UIColor blueColor].CGColor;
sublayer.shadowOffset = CGSizeMake(0, 3);
sublayer.shadowRadius = 5.0;
sublayer.shadowColor = [UIColor blackColor].CGColor;
sublayer.shadowOpacity = 0.8;
sublayer.frame = CGRectMake(posX, posY, 65, 65);
sublayer.borderColor = [UIColor blackColor].CGColor;
sublayer.borderWidth = 2.0;
sublayer.cornerRadius = 10.0;
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = sublayer.bounds;
imageLayer.cornerRadius = 10.0;
imageLayer.contents = (id) [UIImage imageNamed:imgName].CGImage;
imageLayer.masksToBounds = YES;
[sublayer addSublayer:imageLayer];
[myView.layer addSublayer:sublayer];
return myView;
}
I have commented all my code except this, so I'm sure the lag comes from here. Also I've checked with the Allocations tools and my app never exceeded 1Mo. When I'm just putting images without shadow etc. everything works fine.
Try setting a shadowPath on the layer as well. It will need to be a rounded rect since you've got rounded corners on your layer.
CALayer has to calculate where it is drawing, and where to put the shadow, if it doesn't have a shadow path. This has a big effect on animation performance.
Another way to improve performance with CALayers is to set the shouldRasterize property to YES. This stores the layer contents as a bitmap and prevents it having to re-render everything.
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];