CAShapeLayer: different line cap at start/end - ios

I'm using CAShapeLayer to draw a semicircle and I want to have a kCGLineCapRound at the start and a kCGLineCapButt at the end. How can I do that?
UIBezierPath *circlePathMax = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.view.center.x, self.view.center.y) radius:radius startAngle:angle1 endAngle:angle2 clockwise:YES];
CAShapeLayer *circleMax;
circleMax = [CAShapeLayer layer];
circleMax.path = circlePathMax.CGPath;
circleMax.lineCap = kCALineCapRound;
circleMax.fillColor = [UIColor clearColor].CGColor;
circleMax.lineWidth = 10;
circleMax.strokeColor = [UIColor colorWithRed:255.0/255.0f green:255.0f/255.0f blue:255.0f/255.0f alpha:0.7f].CGColor;
circleMax.zPosition = 3;
[self.view.layer addSublayer:circleMax];
I can specify only one generic lineCap

You could use lineCap .butt, and at the end of your semicircle, draw yourself a circle.

This is the trick I used. In my case I had to draw a semicircle with transparency on top of an imageView of a gradient semicircle. This allowed me to use .butt with the background color (with alpha component) as strokeColor to "hide" the squared corners.
let circleMin = CAShapeLayer.init()
let circlePathMin = UIBezierPath.init(arcCenter: myCenter, radius: myRadius, startAngle: startingAngle, endAngle: endingAngle, clockwise: true)
circleMin.path = circlePathMin.cgPath
circleMin.lineCap = .butt
circleMin.fillColor = UIColor.clear.cgColor
circleMin.lineWidth = 14
circleMin.strokeColor = self.containerView.backgroundColor?.withAlphaComponent(0.7).cgColor
circleMin.zPosition = 3
containerView.layer.addSublayer(circleMin)

Related

Objective c : strange behaviour when masking layer with another layer and changing frame

UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:CGPointZero radius:80 startAngle:0 endAngle:(2 * M_PI) clockwise:YES];
CAShapeLayer * layer = [CAShapeLayer new];
layer.strokeColor = [UIColor redColor].CGColor;
layer.fillColor = [UIColor clearColor].CGColor;
layer.lineWidth = 10;
layer.strokeEnd = 1;
layer.position = self.view.center;
layer.lineCap = kCALineCapRound;
[layer setPath : path.CGPath];
[self.view.layer addSublayer : layer];
CAGradientLayer * gradientLayer = [CAGradientLayer layer];
gradientLayer.startPoint = CGPointMake(0.0,0.5);
gradientLayer.endPoint = CGPointMake(1.0,0.5);
gradientLayer.frame = self.view.frame;
NSArray *colors = #[(id)[UIColor greenColor].CGColor,(id)[UIColor blueColor].CGColor,(id)[UIColor yellowColor].CGColor];
gradientLayer.colors = colors;
[gradientLayer setMask : layer];
[self.view.layer addSublayer : gradientLayer];
What Im doing:
Adding CAShapeLayer to view.layer with a path to draw a circle.
Adding CAGradientLayer ( 3 colors ) to view.layer and setingMask to the CAShapeLayer.
Result: with and without mask
The problem :
If I change the frame of the gradient to be on top of the circle ( because I want to see the all the colors on the circle ) its moves the circle as well.
particularly the change to the x and y of the gradient
So instead of
gradientLayer.frame = self.view.frame;
I change the x and y only ( width and hight doesn't causing this problem as far as I checked )
gradientLayer.frame = CGRectMake(100,100, self.view.frame.size.width, self.view.frame.size.height);
And this is the result
Can anyone explain why this is happening and what are the possible solutions ?
What Im trying to achieve is this
And then masking it with the CAShapeLayer but then this problem happens
Thanks in advance
change the shapelayer position, because gradient layer is fullscreen and shapelayer set center position and after you change the frame of gradient layer shapelayer also change with same position.
layer.position = CGPointMake(30, 250);

IOS: UIImageView border white with radius display a strange dark line in 4 corners

I set the border white and radius for my ImageView. But in 4 corner of the ImageView, some dark line appear.
Here is the code I set the color for my ImageView
self.image.layer.cornerRadius = 10;
self.image.layer.borderWidth = 3;
self.image.layer.borderColor = [[UIColor whiteColor] CGColor];
self.image.layer.masksToBounds = YES;
This is a small demo project. My storyboard only contains the ImageView, and I don't set any constraint for my ImageView.
I don't know why this happened, it have tested on simulator and real device but it give a same error
Here is demo project: (it's very simple) https://drive.google.com/file/d/0B_poNaia6t8kT0JLSFJleGxEcmc/view?usp=sharing
Update
Some people give a solution is change the color of border color from whiteColor to clearColor. Of course it will make 4 lines disappear. But if I using clearColor, I don't need to add border to my ImageView.
I need border white for some reason
Updated code
I tried your code actually your image size is big initially I resized the image based on original Image size
UIImage *myIcon = [self imageWithImage:[UIImage imageNamed:#"abc.jpg"] scaledToSize:CGSizeMake(400, 400)];
self.image.image = myIcon;
sometimes corner radius does not work properly so I used UIBezierPath for this concept
UIBezierPath *maskPath;
maskPath = [UIBezierPath bezierPathWithRoundedRect:self.image.bounds byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerTopRight | UIRectCornerBottomLeft | UIRectCornerBottomRight) cornerRadii:CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.view.bounds;
maskLayer.path = maskPath.CGPath;
self.image.layer.mask = maskLayer;
for border color and width use this
swift 3
let maskPath = UIBezierPath(roundedRect: imageView.bounds, byRoundingCorners: ([.topLeft, .topRight, .bottomLeft, .bottomRight]), cornerRadii: CGSize(width: 10.0, height: 10.0))
let borderShape = CAShapeLayer()
borderShape.frame = self.imageView.bounds
borderShape.path = maskPath.cgPath
borderShape.strokeColor = UIColor.white.cgColor
borderShape.fillColor = nil
borderShape.lineWidth = 3
self.imageView.layer.addSublayer(borderShape)
output
Update
CAShapeLayer* borderShape = [CAShapeLayer layer];
borderShape.frame = self.image.bounds;
borderShape.path = maskPath.CGPath;
borderShape.strokeColor = [UIColor whiteColor].CGColor;
borderShape.fillColor = nil;
borderShape.lineWidth = 3;
[self.image.layer addSublayer:borderShape];
Swift
var borderShape: CAShapeLayer = CAShapeLayer.layer
borderShape.frame = self.image.bounds
borderShape.path = maskPath.CGPath
borderShape.strokeColor = UIColor.whiteColor().CGColor
borderShape.fillColor = nil
borderShape.lineWidth = 3
self.image.layer.addSublayer(borderShape)
Output
Code for whole project
Actually you have to use two layers.
self.image.clipsToBounds = YES;
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.image.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(48, 48)];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = self.image.bounds;
maskLayer.path = maskPath.CGPath;
maskLayer.strokeColor = [UIColor redColor].CGColor;
self.image.layer.mask = maskLayer;
CAShapeLayer* frameLayer = [CAShapeLayer layer];
frameLayer.frame = self.image.bounds;
frameLayer.path = maskPath.CGPath;
frameLayer.strokeColor = [UIColor whiteColor].CGColor;
frameLayer.fillColor = nil;
frameLayer.lineWidth = 20;
[self.image.layer addSublayer:frameLayer];
Try clip to bounds:
self.image.clipToBounds = YES
i tried this function written in imageview category:
- (void)setBorderWithRounCornersWithColor:(UIColor *)color{
self.layer.cornerRadius = 5.0f;
self.layer.masksToBounds = YES;
if(color){
self.layer.borderColor=color.CGColor;
self.layer.borderWidth=1.0f;
}
}
at time of using:
[self.imageView setBorderWithRounCornersWithColor:nil];
I check your code you can change this line its work for me
self.image.layer.borderColor = [[UIColor clearColor] CGColor];
when you add a border that add in inside the image border line and when you set rounded border that time your round corner has some transparent part so that part will display in corner side
just change the Color of border color
image1.layer.cornerRadius = 10;
image1.layer.borderWidth = 3;
image1.layer.borderColor = [[UIColor whiteColor] CGColor];
image1.layer.masksToBounds = YES;
image2.layer.cornerRadius = 10;
image2.layer.borderWidth = 3;
image2.layer.borderColor = [[UIColor clearColor] CGColor];
image2.layer.masksToBounds = YES;
recently got same issue, but in slightly different context.
Result with ugly black border:
let image = UIImage(named: "File.png")!
let cornerRadius: CGFloat = 60
let frame = CGRect(origin: .zero, size: image.size)
let path = UIBezierPath(roundedRect: frame, cornerRadius: cornerRadius)
let rounded = UIGraphicsImageRenderer(size: image.size).image { context in
path.addClip()
image.draw(in: frame)
}
but if we add .insetBy(dx: 1, dy: 1) to frame - it will solve the problem.

how to designed a specific shape of scrollView

How iOS we can to achieve a specific shape of the scrollView, the system provides graphical shape of scrollView.
I want to achieve scrollView in a hexagonal or circular shape, Is there some design ideas?
Just do like this:
CAShapeLayer *layer = [CAShapeLayer layer];
// self is the scroll view
layer.frame = self.bounds;
// here is a circular arc
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2) radius:self.bounds.size.width / 2.5 startAngle:-3 / 4 * M_PI endAngle:1 / 3 * M_PI clockwise:YES];
layer.lineWidth = 20;
layer.path = path.CGPath;
layer.fillColor = [UIColor clearColor].CGColor;
layer.strokeColor = [UIColor blackColor].CGColor;
layer.lineCap = kCALineCapRound;
self.layer.mask = layer;
self.layer.masksToBounds = YES;

draw a circular progress view which shows range

A circular view will show progress as you can see I have pointed out, the circular view should be divided in ranges and will show a progress how much percent is reached.
I don't know how to approach with this design and how to divide the view in equal range and shows how much progress user has achieved.
Any suggestion and help will be appreciated.
Thanks
Use two CAShapeLayer,one as background,one as progress
Example
-(CAShapeLayer *)createCircleWithBounds:(CGRect)bounds
Position:(CGPoint)position
StrokeColor:(UIColor*)color
LineWidth:(CGFloat)lineWidth
{
CAShapeLayer* shapelayer = [CAShapeLayer layer];
shapelayer.strokeColor = color.CGColor;
shapelayer.fillColor = [UIColor clearColor].CGColor;
shapelayer.path = [UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:CGRectGetWidth(bounds)/2].CGPath;
shapelayer.bounds = bounds;
shapelayer.position = position;
shapelayer.lineCap = kCALineCapButt;
shapelayer.lineWidth = lineWidth;
return shapelayer;
}
Then
CAShapeLayer * progressLayer = [self createCircleWithBounds:CGRectMake(0, 0, 100, 100) Position:self.view.center StrokeColor:[UIColor whiteColor] LineWidth:5.0];
progressLayer.strokeStart = 0.2;
progressLayer.strokeEnd = 0.8;
[self.view.layer addSublayer:progressLayer];
CAShapeLayer * otherLayer = [self createCircleWithBounds:CGRectMake(0, 0, 100, 100) Position:self.view.center StrokeColor:[UIColor blueColor] LineWidth:5.0];
otherLayer.strokeStart = 0.2;
otherLayer.strokeEnd = 0.5;
[self.view.layer addSublayer:otherLayer];
Then what you need to do is just use some math function to change the otherLayer.strokeEnd when the progress change
You can use CAShapeLayer and CGPath to programatically draw it.

Apply gradient color to arc created with UIBezierPath

I want to create a progress bar that has the form of an arc. The color of the progress bar has to change according to the values.
I created an arc using UIBezierPath bezierPathWithArcCenter. I used the following code:
- (void)viewDidLoad
{
[super viewDidLoad];
int radius = 100;
CAShapeLayer *arc = [CAShapeLayer layer];
arc.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 50) radius:radius startAngle:60.0 endAngle:0.0 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.lineWidth = 15;
[self.view.layer addSublayer:arc];
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 5.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
drawAnimation.removedOnCompletion = NO; // 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"];
}
The result looks like this:
My question is: How to apply a gradient to the color if i.e. the value is <= 50%? I also created an UIButton that generates random CGFloat numbers in order to hook it up with the progress bar. Does anyone have an idea how to tackle this?
The gradient would look something like this:
Any help would be appreciated!
Thank you very much.
Granit
You can use a CAGradientLayer to get the gradient effect, and use the CAShapeLayer as a mask.
e.g.:
- (void)viewDidLoad
{
[super viewDidLoad];
int radius = 100;
CAShapeLayer *arc = [CAShapeLayer layer];
arc.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 50) radius:radius startAngle:60.0 endAngle:0.0 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.lineWidth = 15;
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 5.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
drawAnimation.removedOnCompletion = NO; // 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"];
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.view.frame;
gradientLayer.colors = #[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor blueColor].CGColor ];
gradientLayer.startPoint = CGPointMake(0,0.5);
gradientLayer.endPoint = CGPointMake(1,0.5);
[self.view.layer addSublayer:gradientLayer];
//Using arc as a mask instead of adding it as a sublayer.
//[self.view.layer addSublayer:arc];
gradientLayer.mask = arc;
}
To draw a gradient along a stroke, see this implementation: https://stackoverflow.com/a/43668420/917802
EDIT:
In short, create a custom UIView class, add a radial gradient to it by iterating between two colours at increasing angles, e.g. colour1 = 1 degree, colour2 = 2 degrees etc, all the way up to 360. Then apply a donut mask to that. As you change the strokeEnd value of the masking CAShapeLayer, you also rotate the underlying radial gradient. Because they move together it looks like the stroke itself has a gradient.
The Answers given so far are great, but they are a bit complicated, I think the following logic should be much simpler:
Draw your curve/path of your shape layer .
Close the path on itself, i.e draw the same curve/path but in reverse.
Create a CAShapeLayer and set its path to the path in (2).
Give the shape layer in (3) an arbitrary stroke colour.
Create a CAGradientLayer.
Set the gradient layer mask to the shape layer in (4).
Set the colours of the gradient layer to your desired array.
Add the gradient layer to your original shapeLayer in (1).
func draweCurve(fromPoint: CGPoint, toPoint: CGPoint, x: CGFloat) {
// ------- 1 --------
let curveLayer = CAShapeLayer()
curveLayer.contentsScale = UIScreen.main.scale
curveLayer.frame = CGRect(origin: .zero, size: CGSize(width: 100, height: 100))
curveLayer.fillColor = UIColor.red.cgColor
curveLayer.strokeColor = UIColor.blue.cgColor
let path = UIBezierPath()
path.move(to: fromPoint)
let controlPoint1 = CGPoint(x: fromPoint.x + 40 * 0.45, y: fromPoint.y)
let controlPoint2 = CGPoint(x: toPoint.x - 40 * 0.45, y: toPoint.y)
path.addCurve(to: toPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
curveLayer.path = path.cgPath
// ------- 2 --------
// close the path on its self
path.addCurve(to: fromPoint, controlPoint1: controlPoint2, controlPoint2: controlPoint1)
addGradientLayer(to: curveLayer, path: path)
}
private func addGradientLayer(to layer: CALayer, path: UIBezierPath) {
// ------- 3 --------
let gradientMask = CAShapeLayer()
gradientMask.contentsScale = UIScreen.main.scale
// ------- 4 --------
gradientMask.strokeColor = UIColor.white.cgColor
gradientMask.path = path.cgPath
// ------- 5 --------
let gradientLayer = CAGradientLayer()
// ------- 6 --------
gradientLayer.mask = gradientMask
gradientLayer.frame = layer.frame
gradientLayer.contentsScale = UIScreen.main.scale
// ------- 7 --------
gradientLayer.colors = [UIColor.gray.cgColor, UIColor.green.cgColor]
// ------- 8 --------
layer.addSublayer(gradientLayer)
}

Resources