UIVisualEffectView with mask layer - ios

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
}

Related

How to animate object using bezier path?

Here is my code to draw dashed line
func drawLine() {
let shapeLayer = CAShapeLayer()
shapeLayer.bounds = viewDraw.bounds
shapeLayer.position = CGPoint(x: viewDraw.bounds.width/2, y: viewDraw.bounds.height/2)
shapeLayer.fillColor = nil
shapeLayer.strokeColor = ColorConstants.ThemeColor.cgColor
shapeLayer.lineWidth = 3
shapeLayer.lineJoin = CAShapeLayerLineJoin.round // Updated in swift 4.2
shapeLayer.lineDashPattern = [10]
let path = UIBezierPath(roundedRect: viewDraw.bounds, cornerRadius: viewDraw.bounds.size.height/2)
shapeLayer.path = path.cgPath
viewDraw.layer.addSublayer(shapeLayer)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.duration = 1
shapeLayer.add(animation, forKey: "MyAnimation")
}
Now as per my screenshot I want to animate blue dot to red dot within that dashed line infinite time. When blue dot reaches red dot then it will start again with blue dot points.
Note: This whole screen is dynamic so, Line draw is also dynamic according to screen height
Any help would be appreciated
You are almost there
Now you have to add animation using CAKeyframeAnimation which has property path where you can pass the path where you want to animate
here is working sample code
func drawLine() {
let shapeLayer = CAShapeLayer()
shapeLayer.bounds = self.view.bounds
shapeLayer.position = CGPoint(x: self.view.bounds.width/2, y: self.view.bounds.height/2)
shapeLayer.fillColor = nil
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 3
shapeLayer.lineJoin = CAShapeLayerLineJoin.round // Updated in swift 4.2
shapeLayer.lineDashPattern = [10]
let path = UIBezierPath(roundedRect: self.view.bounds, cornerRadius: self.view.bounds.size.height/2)
shapeLayer.path = path.cgPath
self.view.layer.addSublayer(shapeLayer)
let animation1 = CABasicAnimation(keyPath: "strokeEnd")
animation1.fromValue = 0
animation1.duration = 1
shapeLayer.add(animation1, forKey: "MyAnimation")
// Create squareView and add animation
let animation = CAKeyframeAnimation(keyPath: #keyPath(CALayer.position))
animation.duration = 1
animation.repeatCount = MAXFLOAT
animation.path = path.cgPath
let squareView = UIView()
// Don't worry about x,y and width and height it will not affect the animation
squareView.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
squareView.backgroundColor = .lightGray
view.addSubview(squareView)
// You can also pass any unique string value for key
squareView.layer.add(animation, forKey: nil)
}
You can see this tutorial for more help http://mathewsanders.com/animations-in-swift-part-two/

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

How to add a shadow to a kCAFillRuleEvenOdd mask layer

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

How to mask and add shadow to a UIView

I'm making a custom view that i want to mask and to add shadow to it.
the masking:
let p = UIBezierPath()
p.moveToPoint(CGPointMake(20, 20))
p.addLineToPoint(CGPointMake(100, 20))
p.addLineToPoint(CGPointMake(100, 50))
p.addLineToPoint(CGPointMake(110, 55))
p.addLineToPoint(CGPointMake(100, 60))
p.addLineToPoint(CGPointMake(100, 100))
p.addLineToPoint(CGPointMake(20, 100))
p.closePath()
let s = CAShapeLayer()
s.frame = layer.bounds
s.path = p.CGPath
s.fillColor = UIColor.greenColor().CGColor
layer.mask = s
the masking works, now i want to add a shadow.
but its not working.
i tried to add shadow to the main layer and nothing happens.
layer.shadowColor = UIColor.yellowColor().CGColor
layer.shadowRadius = 10
layer.shadowOpacity = 0.9
layer.shadowOffset = CGSizeZero
i tried to add it to the mask layer and i got the main view masked with a shadow.
s.shadowColor = UIColor.yellowColor().CGColor
s.shadowRadius = 10
s.shadowOpacity = 0.9
s.shadowOffset = CGSizeZero
Any suggestions how to add this yellow shadow to the masked view?
Thanks
Thanks #WilsonXJ
I changed mask to addSubLayer.
This is the answer that worked for me:
let p = UIBezierPath()
p.moveToPoint(CGPointMake(20, 20))
p.addLineToPoint(CGPointMake(100, 20))
p.addLineToPoint(CGPointMake(100, 50))
p.addLineToPoint(CGPointMake(110, 55))
p.addLineToPoint(CGPointMake(100, 60))
p.addLineToPoint(CGPointMake(100, 100))
p.addLineToPoint(CGPointMake(20, 100))
p.closePath()
let s = CAShapeLayer()
s.fillColor = UIColor.whiteColor().CGColor
s.frame = layer.bounds
s.path = p.CGPath
layer.backgroundColor = UIColor.clearColor().CGColor
layer.addSublayer(s)
layer.masksToBounds = true
layer.shadowColor = UIColor.yellowColor().CGColor
layer.shadowOffset = CGSizeZero
layer.shadowOpacity = 0.9
layer.shadowPath = p.CGPath
layer.shadowRadius = 10
I don't think that current answer is the right one, because there is no layer.mask usage anymore.
In case when you need to use layer.mask and to drop shadow of masked layer - the obvious solution is to add another layer below masked layer that will have same shape as layer.mask and drop it's shadow
example:
let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 500, height: 500)))
view.backgroundColor = .white
PlaygroundPage.current.liveView = view
let path: CGPath = ...
let maskedView = UIView(frame: path.boundingBox)
maskedView.center = view.center
maskedView.backgroundColor = .green
view.addSubview(maskedView)
let maskLayer = CAShapeLayer()
maskLayer.frame = maskedView.bounds
maskLayer.path = path
maskedView.layer.mask = maskLayer
let shadowLayer = CAShapeLayer()
shadowLayer.path = path
shadowLayer.frame = maskedView.frame
shadowLayer.shadowOpacity = 0.4
shadowLayer.shadowRadius = 2
shadowLayer.shadowColor = UIColor.black.cgColor
shadowLayer.shadowOffset = CGSize(width: 4, height: 4)
maskedView.superview!.layer.insertSublayer(shadowLayer, below: maskedView.layer)

How to add a shadow and CAShapeLayer to the same UIView

I'm trying to add a 6px radius to the bottom left and right corners of my view innerView, as well as a shadow to the whole of innerView. innerView is enclosed in a view called view2.
The only way I can get this to work is by using the following code:
let containerLayer = CALayer()
containerLayer.shadowColor = UIColor.blackColor().CGColor
containerLayer.shadowOffset = CGSizeMake(1, 2)
containerLayer.shadowOpacity = 0.2
containerLayer.shadowRadius = 2
let maskPath = UIBezierPath(roundedRect: innerView.bounds, byRoundingCorners: UIRectCorner.BottomLeft.union(.BottomRight), cornerRadii: CGSize(width: 6, height: 6)).CGPath
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath
innerView.layer.mask = maskLayer
containerLayer.addSublayer(innerView.layer)
view2.layer.addSublayer(containerLayer)
However, since view2 is actually the contentView of a UITableViewCell, this code disables scrolling of the UITableView when dragging on containerLayer...
Swift 4 :
This Code Helped Me To Drop A Shadow To UIVIEW And Add CornerRadious Using UIBezierPath. Set Your View's Background Color As ClearColor
yourView.backgroundColor=UIColor.clear
let path1 = UIBezierPath(roundedRect:yourView.bounds,
byRoundingCorners:[.topRight, .bottomLeft],
cornerRadii: CGSize(width: 20, height: 20))
let maskLayer1 = CAShapeLayer()
maskLayer1.path = path1.cgPath
maskLayer1.fillColor = UIColor.white.cgColor
maskLayer1.shadowColor = UIColor.darkGray.cgColor
maskLayer1.shadowPath = maskLayer1.path
maskLayer1.shadowOffset = CGSize(width: 0.0, height: 2.0)
maskLayer1.shadowOpacity = 0.6
maskLayer1.shadowRadius = 2
yourView.layer.insertSublayer(maskLayer1, at: 0)
Solved it by embedding innerView in another view. I applied the shadow to the new view and the radius to the innerView:
func addShadowTo(shadowView: UIView, andRadiusTo radiusView: UIView) {
let maskPath = UIBezierPath(roundedRect: radiusView.bounds, byRoundingCorners: UIRectCorner.BottomLeft.union(.BottomRight), cornerRadii: CGSize(width: 6, height: 6)).CGPath
shadowView.layer.shadowPath = maskPath
shadowView.layer.shadowColor = UIColor.blackColor().CGColor
shadowView.layer.shadowOffset = CGSizeMake(1, 2)
shadowView.layer.shadowOpacity = 0.2
shadowView.layer.shadowRadius = 2
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath
radiusView.layer.mask = maskLayer
}
If you are using Autolayout, then remove UIBezierPath. Try this code and custom one for yourself:
//This code custom a view with shadow and corner radius = 6
let shadowLayer: CALayer = shadowView.layer
shadowView.clipsToBounds = false
shadowLayer.shadowColor = 'your color'
shadowLayer.shadowOffset = CGSizeZero
shadowLayer.shadowOpacity = 1
shadowLayer.shadowRadius = 3
shadowLayer.shouldRasterize = true
let innerLayer: CALayer = innerView.layer
innerLayer.cornerRadius = 6
innerView.clipsToBounds = true
innerLayer.borderColor = 'your color'.CGColor
innerLayer.borderWidth = 1
The "innerView" is inside "shadowView" (on my xib file).

Resources