17

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:

Current State

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.

dehlen
  • 7,325
  • 4
  • 43
  • 71
  • Have you tried adding the visual effect view on top of the mapView instead of as a subview? – Nailer Sep 20 '16 at 14:17
  • Not sure if this is any use, but this guy has listed his playground code to do what you want: http://stackoverflow.com/questions/38510476/blur-on-mkmapview – Jack Chorley Sep 20 '16 at 14:38
  • You mean instead of `self.mapView.addSubview(blur)` writing `self.view.insertSubview(blur, aboveSubview: self.mapView)` ? I tried that but could not see any changes. – dehlen Sep 20 '16 at 14:39
  • @JackC this is basically the same code as mine. I just checked: When I remove the circle mask the blur on the mapview works. But if I add the circle mask code the blur is gone... – dehlen Sep 20 '16 at 14:47
  • @dehlen at this moment I trying to find solution too.)))) Yes, if I remove layer.mask blur is working, but visual effect adding blur inside circle too. – dev.nikolaz Sep 20 '16 at 15:03
  • @dev.nikolaz thank you very much for your help ! Would it help if I created a simple playground so you can test out your ideas quicker ? – dehlen Sep 20 '16 at 15:15
  • @dehlen, I trying to found solution for objC, but I try to help in swift too) btw u should read this topic, https://forums.developer.apple.com/thread/50854 – dev.nikolaz Sep 20 '16 at 15:52

2 Answers2

20

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!

Jack Chorley
  • 2,309
  • 26
  • 28
4

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
}