Lines drawn using bezierPath and CAShapeLayer, not smooth - objective-c

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];
}

Related

SKShapeNode.Get Radius?

Is it possible to get an SKShapeNode's radius value?
The problem is, I need to create an identical circle after the user releases it, whilst removing the first circle from the view.
SKShapeNode *reticle = [SKShapeNode shapeNodeWithCircleOfRadius:60];
reticle.fillColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.3];
reticle.strokeColor = [UIColor clearColor];
reticle.position = CGPointMake(location.x - 50, location.y + 50);
reticle.name = #"reticle";
reticle.userInteractionEnabled = YES;
[self addChild:reticle];
[reticle runAction:[SKAction scaleTo:0.7 duration:3] completion:^{
[reticle runAction:[SKAction scaleTo:0.1 duration:1]];
}];
Then
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
....
SKNode *node = [self childNodeWithName:#"reticle"];
//take the position of the node, draw an imprint
/////////Here is where I need to get the circle radius.
[node removeFromParent];
}
How does one take the circle radius, so I can then say
SKShapeNode *imprint = [SKShapeNode shapeNodeWithCircleOfRadius:unknownValue];
I've tried, making an exact copy of the circle with
SKShapeNode *imprint = (SKShapeNode *)node;
However, this still follows the animation where as I need it to stop, at the point it was at. I don't want to take a "stopAllAnimations" approach.
You can use the shape node's path property to determine the bounding box of the shape with CGPathGetBoundingBox(path). From the bounds, you can compute the radius of the circle by dividing the width (or height) by 2. For example,
CGRect boundingBox = CGPathGetBoundingBox(circle.path);
CGFloat radius = boundingBox.size.width / 2.0;
Updated for Swift 5
myVariable.path.boundingBox.width / 2

Circular-slider weird behaviour while animating with CA

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.

Strange behavior with UIBezierPath addClip

I'm trying to learn CoreGraphics and I have encountered a strange behavior.
I draw a rectangle and in it I draw given amount of diamonds,the shapes can be drawn with different filling (empty, filled and with stripes) my draw rect function looks like this:
- (void)drawRect:(CGRect)rect
{
UIBezierPath* roundedRect = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:9.0];
//don't draw where the round corners "cut" the rectangle
[roundedRect addClip];
//set a white background
[[UIColor whiteColor] setFill];
UIRectFill(self.bounds);
//set a black frame
[[UIColor darkGrayColor] setStroke];
[roundedRect stroke];
self.shade = STRIPED;
self.color = [UIColor greenColor];
self.number = 3;
rectOffset = self.bounds.size.width / (self.number * 2);
[self drawDiamondNumberOfTimes:self.number startOrigin:self.bounds.origin];
}
drawDiamondNumberOfTimes:startOrigin: is a recursive function that calculates the rectangle in which the shape will be drawing and draw the diamond boundaries using stroke
- (void) drawDiamondNumberOfTimes:(int) p_times startOrigin:(CGPoint) p_origin
{
if( p_times > 0)
{
CGRect drawArea;
drawArea.origin = CGPointMake(p_origin.x + rectOffset-shapeSize.width/2, self.bounds.size.height/4);
drawArea.size = shapeSize;
UIBezierPath *diamond = [[UIBezierPath alloc] init];
[diamond moveToPoint:CGPointMake(drawArea.origin.x, drawArea.origin.y+ shapeSize.height/2)];
[diamond addLineToPoint:CGPointMake(drawArea.origin.x+shapeSize.width/2, drawArea.origin.y)];
[diamond addLineToPoint:CGPointMake(drawArea.origin.x+shapeSize.width, drawArea.origin.y+ shapeSize.height/2)];
[diamond addLineToPoint:CGPointMake(drawArea.origin.x+shapeSize.width/2, drawArea.origin.y+ shapeSize.height)];
[diamond closePath];
[self.color setStroke];
[diamond stroke];
[self drawShadeOfDraw:diamond atRect:drawArea];
drawArea.origin.x += rectOffset + shapeSize.width/2;
[self drawDiamondNumberOfTimes:p_times-1 startOrigin:drawArea.origin ];
}
}
drawShadeOfDraw:atRect: set the different filling, where the strange behavior occurs.
With empty and solid fills it works perfect but with stripes, if I write [p_symbol addClip] then I get always one diamond striped even if self.number is set to 2 or 3. Without [p_symbol addClip] I get the correct number of diamonds but, of course, the stripes are all over the rectangle, here is the code for drawShadeOfDraw:atRect:
- (void)drawShadeOfDraw:(UIBezierPath*)p_symbol atRect:(CGRect)p_drawArea
{
switch (self.shade)
{
case STRIPED:
{
[p_symbol addClip];
for( int y = p_drawArea.origin.y; y < p_drawArea.origin.y+ p_drawArea.size.height; y+= 6)
{
[p_symbol moveToPoint:CGPointMake(p_drawArea.origin.x, y)];
[p_symbol addLineToPoint:CGPointMake(p_drawArea.origin.x+shapeSize.width, y)];
[self.color setStroke];
[p_symbol stroke];
}
break;
}
case SOLID:
{
[self.color setFill];
[p_symbol fill];
break;
}
default:
{
break;
}
}
}
Here are some images:
What am I doing wrong?
Adding to the clip is semi-permanent. Once the clipping area has been "reduced", you can't grow it again, per se. You can only restore it to a previous state. To do that you call CGContextSaveGState() before setting up context state (including clipping), do some drawing with that state, and then call CGContextRestoreGState() afterward to restore the context state to what it was before.
So, I suggest that you bracket your two methods with calls to save and restore the context state. Use UIGraphicsGetCurrentContext() to get a reference to the current context.

How to add shadow to a group of CALayer using CATransform3D

How can I add shadow to a group of CALayers?
I have a "FoldingView"-class which maintans several "SliceView"-classes. Each sliceview's layer will be givven a CATransform3D where I'm using the perspective property (.m34 = 1.0 / -1000).
How can I add a shadow with good visual logic? This is what I've been thinking so far:
I could get the path of each slice and combine these to get a shadow path.
I don't know how the get a path of CALayer when the layer is using CATransform3D
It might work visually, but I'm afraid it won't be totally right if the light is supposed to come from top left.
I could just apply standard CALayer shadow to all layers
It does not look good due to the shadow is overlapping each other
If anyone has any other suggestions or know how to code idea number 1 I'll be very happy! Here is a link to the sample project you see screenshot from.
Download zipped application with code
This seems to work as wanted. This is done per sliceview.
- (UIBezierPath *)shadowPath
{
if(self.progress == 0 && self.position != VGFoldSliceCenter)
{
UIBezierPath *path = [UIBezierPath bezierPath];
return path;
}
else
{
CGPoint topLeft = pointForAnchorPointInRect(CGPointMake(0, 0), self.bounds);
CGPoint topRight = pointForAnchorPointInRect(CGPointMake(1, 0), self.bounds);
CGPoint bottomLeft = pointForAnchorPointInRect(CGPointMake(0, 1), self.bounds);
CGPoint bottomRight = pointForAnchorPointInRect(CGPointMake(1, 1), self.bounds);
CGPoint topLeftTranslated = [self.superview convertPoint:topLeft fromView:self];
CGPoint topRightTranslated = [self.superview convertPoint:topRight fromView:self];
CGPoint bottomLeftTranslated = [self.superview convertPoint:bottomLeft fromView:self];
CGPoint bottomRightTranslated = [self.superview convertPoint:bottomRight fromView:self];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:topLeftTranslated];
[path addLineToPoint:topRightTranslated];
[path addLineToPoint:bottomRightTranslated];
[path addLineToPoint:bottomLeftTranslated];
[path closePath];
return path;
}
}

Showing UIBezierPath on view

In one of my methods i have this code:
-(void)myMethod {
UIBezierPath *circle = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(75, 100, 200, 200)];
}
How do i get it to show on the view?
I tried addSubview but it gave me an incompatible type error because its expecting a UIView.
I'm sure this must be simple.
Thanks
Just thought I'd add that you don't have to necessarily draw this in a UIView's "drawRect:" method. You can draw it anywhere you'd like to provided you do it inside of a UIGraphics image context. I do this all of the time when I don't want to create a subclass of UIView. Here's a working example:
UIBezierPath *circle = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(75, 100, 200, 200)];
//you have to account for the x and y values of your UIBezierPath rect
//add the x to the width (75 + 200)
//add the y to the height (100 + 200)
UIGraphicsBeginImageContext(CGSizeMake(275, 300));
//this gets the graphic context
CGContextRef context = UIGraphicsGetCurrentContext();
//you can stroke and/or fill
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor lightGrayColor].CGColor);
[circle fill];
[circle stroke];
//now get the image from the context
UIImage *bezierImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *bezierImageView = [[UIImageView alloc]initWithImage:bezierImage];
Now just add the UIImageView as a subview.
Also, you can use this for other drawing too. Again, after a little bit of setup, it works just like the drawRect: method.
//this is an arbitrary size for example
CGSize aSize = CGSizeMake(50.f, 50.f);
//this can take any CGSize
//it works like the frame.size would in the drawRect: method
//in the way that it represents the context's size
UIGraphicsBeginImageContext(aSize);
//this gets the graphic context
CGContextRef context = UIGraphicsGetCurrentContext();
//you can do drawing just like you would in the drawRect: method
//I am drawing a square just for an example to show you that you can do any sort of drawing in here
CGContextMoveToPoint(context, 0.f, 0.f);
CGContextAddLineToPoint(context, aSize.width, 0.f);
CGContextAddLineToPoint(context, aSize.width, aSize.height);
CGContextAddLineToPoint(context, 0.f, aSize.height);
CGContextClosePath(context);
//you can stroke and/or fill
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor lightGrayColor].CGColor);
CGContextDrawPath(context, kCGPathFillStroke);
//now get the image from the context
UIImage *squareImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *squareImageView = [[UIImageView alloc]initWithImage:squareImage];
Edit: One thing I should add is that for any modern day drawing of this kind, you should be swapping out
UIGraphicsBeginImageContext(size);
for
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
This will draw your graphics correctly for retina and non retina displays.
FYI, UIGraphicsBeginImageContext(size) is equivalent to UIGraphicsBeginImageContextWithOptions(size, FALSE, 1.f) which is fine for none retina displays that may have some transparency.
However, if you don't need transparency, it is more optimized to pass in TRUE for the opaque argument.
The safest and recommended way of drawing is to pass in [[UIScreen mainScreen]scale] as the scale argument.
So for the example(s) above, you would use this instead:
UIGraphicsBeginImageContextWithOptions(aSize, FALSE, [[UIScreen mainScreen] scale]);
For more info, check out Apple's docs.
You can draw it using either fill or stroke methods for example in custom view's drawInRect: implementation:
- (void)drawRect:(CGRect)rect {
// Drawing code
UIBezierPath *circle = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(75, 100, 200, 200)];
[circle fill];
}
You can also add UIBezierPath to UIView without subclassing by using a CAShapeLayer.
For example, to add your path as a 3pt white line centered in a UIView:
UIBezierPath *mybezierpath = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(0, 0, 100, 100)];
CAShapeLayer *lines = [CAShapeLayer layer];
lines.path = mybezierpath.CGPath;
lines.bounds = CGPathGetBoundingBox(lines.path);
lines.strokeColor = [UIColor whiteColor].CGColor;
lines.fillColor = [UIColor clearColor].CGColor; /*if you just want lines*/
lines.lineWidth = 3;
lines.position = CGPointMake(self.myview.frame.size.width/2.0, self.myview.frame.size.height/2.0);
lines.anchorPoint = CGPointMake(.5, .5);
[self.myview.layer addSublayer:lines];
Drawing is the exclusive provision of views. Make a custom view, give it your path, and implement the view's drawRect: method to fill and/or stroke the path.
In Swift 2.0:
let path = UIBezierPath()
let p1 = CGPointMake(0,self.view.frame.height/2)
let p3 = CGPointMake(self.view.frame.width,self.view.frame.height/2)
path.moveToPoint(p1)
path.addQuadCurveToPoint(p3, controlPoint: CGPoint(x: self.view.frame.width/2, y: 0))
let line = CAShapeLayer()
line.path = path.CGPath;
line.strokeColor = UIColor.blackColor().CGColor
line.fillColor = UIColor.redColor().CGColor
view.layer.addSublayer(line)