Creating a transparent hole on UIVisualEffect view - ios

I'm trying to create a transparent hole kind of view on the UIVisualEffectView. I was following the solution given here. I have attached my sample code which I worked on here. I'm trying to create a transparent view whose frame is taken from the image view behind the blurry view. Anyone could tell me what is that I'm doing wrong here?

You can use a UIBezierPath to create the mask you want.
blurView.layer.mask = ({
CGRect roundedRect = self.bounds;
roundedRect.origin.x = roundedRect.size.width / 4.0f;
roundedRect.origin.y = roundedRect.size.height / 4.0f;
roundedRect.size.width /= 2.0f;
roundedRect.size.height /= 2.0f;
CGFloat cornerRadius = roundedRect.size.height / 2.0f;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.bounds];
UIBezierPath *croppedPath = [UIBezierPath bezierPathWithRoundedRect:roundedRect cornerRadius:cornerRadius];
[path appendPath:croppedPath];
[path setUsesEvenOddFillRule:YES];
CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = path.CGPath;
mask.fillRule = kCAFillRuleEvenOdd;
mask;
});

Swift 5 version of the solution for lazy ones :)
var roundedRect = visualEffectsView.bounds
roundedRect.origin.x = roundedRect.size.width / 4
roundedRect.origin.y = roundedRect.size.height / 4
roundedRect.size.width = roundedRect.size.width / 2
roundedRect.size.height = roundedRect.size.height / 2
let cornerRadius = roundedRect.size.height / 2
let path = UIBezierPath(rect: visualEffectsView.bounds)
let croppedPath = UIBezierPath(roundedRect: roundedRect, cornerRadius: cornerRadius)
path.append(croppedPath)
path.usesEvenOddFillRule = true
let mask = CAShapeLayer()
mask.path = path.cgPath
mask.fillRule = .evenOdd
visualEffectsView.layer.mask = mask

Related

How to fill a bezier path with control points with gradient color

I need to fill my bezier path with a gradient color. But the effect is not what I expected, I know this is caused by the drawing direction.
How can I achieve the effect of the second picture?
my bezierpath code
CGFloat deltaX = lineChartPoint.x - previousLineChartPoint.x;
CGFloat controlPointX = previousLineChartPoint.x + (deltaX / 2);
CGPoint controlPoint1 = CGPointMake(controlPointX, previousLineChartPoint.y);
CGPoint controlPoint2 = CGPointMake(controlPointX, lineChartPoint.y);
[bezierPath addCurveToPoint:CGPointMake(lineChartPoint.x, lineChartPoint.y) controlPoint1:controlPoint1 controlPoint2:controlPoint2];
my gradientLayer code
self.gradientLayer = [CAGradientLayer layer];
self.gradientLayer.frame = CGRectMake(kVPadding, 0, CGRectGetWidth(self.bounds) - 2 * kVPadding, CGRectGetHeight(self.bounds));
self.gradientLayer.colors = #[(__bridge id)[UIColor colorWithHex:0x8362FC alpha:0.9].CGColor,(__bridge id)[UIColor colorWithHex:0x517DF7 alpha:0.1].CGColor];
self.gradientLayer.locations=#[#0.0,#1.0];
self.gradientLayer.startPoint = CGPointMake(0.0,0.0);
self.gradientLayer.endPoint = CGPointMake(0.0,1);
[self.layer addSublayer:self.gradientLayer];
self.gradientShapeLayer = [[CAShapeLayer alloc] init];
self.gradientLayer.mask = self.gradientShapeLayer;
self.gradientShapeLayer.path = bezierPath.CGPath;
#Dragonthoughts

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;

CAShapeLayer: different line cap at start/end

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)

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