How to add a shadow to a kCAFillRuleEvenOdd mask layer - ios

I have a custom view for tutorials.
the hall screen is black and there is a "hole" in the show cased view.
I want to add a shadow around the "hole"
i tried this:
backgroundColor = UIColor.blackColor()
alpha = 0.5
// Create the shape to mask
let path = createPathOfShape(shape)
//this masking works - adding the "hole"
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.fillColor = UIColor.blackColor().CGColor
maskLayer.fillRule = kCAFillRuleEvenOdd
maskLayer.path = path.CGPath
layer.mask = maskLayer
//this is not working - adding the shadow to the "hole"
let glow = CAShapeLayer()
glow.frame = bounds
glow.fillColor = UIColor.redColor().CGColor
glow.path = path.CGPath
glow.shadowColor = UIColor.blackColor().CGColor
glow.shadowRadius = 10
glow.shadowOffset = CGSizeZero
glow.shadowOpacity = 0.5
layer.addSublayer(glow)
Any suggestions?
Thanks

Related

Add subtract mask on top of a UIView

I want to put a UIView on top of another UIView as a subtract mask. Is it possible to do that?
I have a rectangular photo and I want to put a mask on top of it that has black rounded corners. I don't want to put the rounded corners on the photo itself. Essentially the mask is kind of like a photo frame that you can look through.
I did that once using this as reference. It's basically setting up a CAShapeLayer on a UIView on the top of your UIImageView. This is what you need:
// Add image view to the root view
let image = UIImage(named: "SomeImage.jpg")
let imageView = UIImageView(frame: view.bounds)
imageView.image = image
view.addSubview(imageView)
// Add mask view on the of image view
let maskView = UIView(frame: view.bounds)
maskView.backgroundColor = .black
view.addSubview(maskView)
// The layer that defines your maskView's behavior
let maskLayer = CAShapeLayer()
maskLayer.frame = maskView.bounds
// Create the frame for the circle.
let radius: CGFloat = 100.0
let roundedRectPath = UIBezierPath(roundedRect: maskView.frame, cornerRadius: radius)
let path = UIBezierPath(rect: maskView.bounds)
// Append the rounded rect to the external path
path.append(roundedRectPath)
maskLayer.fillRule = .evenOdd
maskLayer.path = path.cgPath
maskView.layer.mask = maskLayer
And here's the result:
Hope that helps!
I have a similar idea as other mask, But just use a UIView mask.
let maskView = UIView.init()
maskView.frame = view.bounds
let shape = CAShapeLayer.init()
shape.path = UIBezierPath.init(ovalIn: maskView.bounds).cgPath
shape.fillColor = UIColor.white.cgColor
maskView.layer.addSublayer(shape)
maskView.backgroundColor = UIColor.init(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.0)
view.mask = maskView // view is the imageView.

Can I change the UIBezierPath frame after it is created

Its coordinates and size ?
path = UIBezierPath(rect: rect)
fillLayer = CAShapeLayer()
fillLayer.path = path.cgPath
or do I need to recreate the path and set it to fillLayer.path again

Chopped edge around border of image

I have an image with a border
let smallicon: UIImageView = {
let smallicon = UIImageView()
smallicon.layer.borderWidth = 2
smallicon.layer.borderColor = UIColor.whiteColor().CGColor
smallicon.hidden = true
return smallicon
}()
The problem is that is has tiny chopped edge around border (small image with yellow and black lines)
How to get rid of it ?
Solution
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Create a view with red background for demonstration
let v = UIView(frame: CGRectMake(0, 0, 100, 100))
v.center = view.center
v.backgroundColor = UIColor.redColor()
view.addSubview(v)
// Add rounded corners
let maskLayer = CAShapeLayer()
maskLayer.frame = v.bounds
maskLayer.path = UIBezierPath(roundedRect: v.bounds, byRoundingCorners: .TopRight | .TopLeft, cornerRadii: CGSize(width: 25, height: 25)).CGPath
v.layer.mask = maskLayer
// Add border
let borderLayer = CAShapeLayer()
borderLayer.path = maskLayer.path // Reuse the Bezier path
borderLayer.fillColor = UIColor.clearColor().CGColor
borderLayer.strokeColor = UIColor.greenColor().CGColor
borderLayer.lineWidth = 5
borderLayer.frame = v.bounds
v.layer.addSublayer(borderLayer)
}
}
NOTE! in order this to work target view must have frame property setup with size. without size your view will not be seen at all

UIVisualEffectView with mask layer

I am trying to blur a MKMapView while also displaying a circle mask above it. To better visualize what I mean with this you can find an image of my current state attached:
This shows nearly what I want but the background (the map) should be blurred which is not the case in this picture. I tried to work with UIVisualEffectView, but it seems I am doing something wrong there. Here is what I tried:
func createOverlay(at: CGPoint) {
var blur: UIView!
blur = UIVisualEffectView (effect: UIBlurEffect (style: UIBlurEffectStyle.dark))
blur.frame = self.mapView.frame
blur.isUserInteractionEnabled = false
self.mapView.addSubview(blur)
let circleSize: CGFloat = 200
let path = UIBezierPath (
roundedRect: blur.frame,
cornerRadius: 0)
let circle = UIBezierPath (
roundedRect: CGRect (origin: CGPoint(x:at.x - circleSize/2, y: at.y-circleSize/2),
size: CGSize (width: circleSize, height: circleSize)), cornerRadius: circleSize/2)
path.append(circle)
path.usesEvenOddFillRule = true
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
maskLayer.fillRule = kCAFillRuleEvenOdd
let borderLayer = CAShapeLayer()
borderLayer.path = circle.cgPath
borderLayer.strokeColor = UIColor.white.cgColor
borderLayer.lineWidth = 10
blur.layer.addSublayer(borderLayer)
blur.layer.mask = maskLayer
}
To summarize here is my question:
How do I have to change my code in order to blur the mapview aswell as show the cirlce mask above it ?
EDIT
Just found out that the blur of the mapview works if I remove the code to add the circle mask... Maybe this is of any interest in order to solve this problem.
Ive now found a solution after digging around for a bit. Im assuming you're using Xcode 8 as the layer.mask has a known bug where you cannot have both a blur, and a mask on the same layer.
After messing around with a playground I have fixed the problem, so will try to adapt your code to match my solution. If you use the "mask" property of the blurView instead, then you should have no issues. There has been a bug report made to Apple I believe in regards to the layer.mask not working
This is your current code at the end
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
maskLayer.fillRule = kCAFillRuleEvenOdd
let borderLayer = CAShapeLayer()
borderLayer.path = circle.cgPath
borderLayer.strokeColor = UIColor.white.cgColor
borderLayer.lineWidth = 10
blur.layer.addSublayer(borderLayer)
blur.layer.mask = maskLayer
Instead of this, try using the following:
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
maskLayer.fillRule = kCAFillRuleEvenOdd
let borderLayer = CAShapeLayer()
borderLayer.path = circle.cgPath
borderLayer.strokeColor = UIColor.white.cgColor
borderLayer.fillColor = UIColor.clear.cgColor //Remember this line, it caused me some issues
borderLayer.lineWidth = 10
let maskView = UIView(frame: self.view.frame)
maskView.backgroundColor = UIColor.black
maskView.layer.mask = maskLayer
blur.layer.addSublayer(borderLayer)
blur.mask = maskView
Let me know if you have any issues!
In addition to Jack's solution, if you are on iOS >11 then just set blur.layer.mask to the maskLayer:
if #available(iOS 11.0, *) {
blur.layer.mask = maskLayer
} else {
let maskView = UIView(frame: self.view.frame)
maskView.backgroundColor = UIColor.black
maskView.layer.mask = borderLayer
blur.mask = maskView
}

iOS Clean corners for rounded profile image

So I followed an AppCoda tutorial on rounding the corners of a profile image, and it worked fine, except for one thing. Wherever the image was rounded, there is a bit of bleed-through from the image (especially if a white border is used).
self.imageview.image = image
self.imageview.layer.cornerRadius = 10.0
self.imageview.layer.borderWidth = 3.0
self.imageview.layer.borderColor = UIColor.whiteColor().CGColor
self.imageview.clipsToBounds = true
You can also add a mask which is inset a little, if you want:
let path = UIBezierPath(roundedRect: CGRectInset(imageView.bounds, 0.5, 0.5), cornerRadius: 10.0)
let mask = CAShapeLayer()
mask.path = path.CGPath
imageview.layer.mask = mask
You could create a mask over the rectangle. This seems to give clean edges, at least in Playground. Here is the code, but you will need to modify it a bit to get rounded inner rect.
// simple red rect
var view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
view.backgroundColor = UIColor.redColor()
view.layer.borderColor = UIColor.whiteColor().CGColor
view.layer.borderWidth = 6.0
// path for the mask
let rectanglePath = UIBezierPath(roundedRect:view.bounds, cornerRadius: 20)
// applying the mask over the view
let maskLayer = CAShapeLayer()
maskLayer.frame = view.bounds
maskLayer.path = rectanglePath.CGPath
view.layer.mask = maskLayer
A simple solution is that you can enlarge layer's bounds a little bit to cover the edge of view's image:
CGFloat offset = 1.f; // .5f is also good enough
self.imageview.image = image;
self.imageview.layer.cornerRadius = 10.0;
self.imageview.layer.borderWidth = 3.0 + offset;
self.imageview.layer.borderColor = UIColor.whiteColor().CGColor;
self.imageview.layer.masksToBounds = YES;
[self.imageview.layer setBounds:CGRectMake(-offset,
-offset,
CGRectGetWidth(self.imageview.frame) + offset * 2.f,
CGRectGetHeight(self.imageview.frame) + offset * 2.f)];

Resources