Uivisualeffectview with Mask Layer

UIVisualEffectView with mask layer

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!

Add UILabel Mask to UIVisualEffectView

You need to assign your UILabel as maskView of your UIVisualEfectView and the trick is that you need add a container for your UIVisualEfectView and your UILabel and set the background color for this container view as UIColor.clear

self.visualEfectView.mask = self.label
self.visualEfectView.layer.masksToBounds = true

View Hierarchy Setup

Sample Image

Result

Sample Image

Recreating a masked blur effect in SwiftUI

I think I nearly have a solution, I can use a viewmodifer to render the result twice on top of each other with a ZStack, I can blur one view, and use a mask to knock a hole in it.

blur

import SwiftUI

struct TestView2: View {

@State var position: CGPoint = .zero

var simpleDrag: some Gesture {
DragGesture()
.onChanged { value in
self.position = value.location
}
}

var body: some View {
ZStack {

Circle()
.fill(Color.green)
.frame(height: 200)

Circle()
.fill(Color.pink)
.frame(height: 200)
.offset(x: 50, y: 100)

Circle()
.fill(Color.orange)
.frame(height: 100)
.offset(x: -50, y: 00)
}
.maskedBlur(position: $position)
.gesture(
simpleDrag
)

}
}

struct MaskedBlur: ViewModifier {

@Binding var position: CGPoint

/// Render the content twice
func body(content: Content) -> some View {

ZStack {
content

content
.blur(radius: 10)
.mask(
Hole(position: $position)
.fill(style: FillStyle(eoFill: true))
.frame(maxWidth: .infinity, maxHeight: .infinity)
)
}

}
}

extension View {
func maskedBlur(position: Binding<CGPoint>) -> some View {
self.modifier(MaskedBlur(position: position))
}
}

struct Hole: Shape {

@Binding var position: CGPoint

func path(in rect: CGRect) -> Path {
var path = Path()

path.addRect(UIScreen.main.bounds)

path.addEllipse(in: CGRect(x: position.x, y: position.y, width: 200, height: 200))

return path
}
}

#if DEBUG

struct TestView2_Previews: PreviewProvider {
static var previews: some View {
TestView2()
}
}

#endif

Swift how to mask shape layer to blur layer

  • Set maskLayer.fillRule to evenOdd, even when not inversed.

      if inverse {
    path.append(UIBezierPath(rect: self.bounds))
    }
    maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
  • create the circleTrackPath by using a big circle and a smaller circle.

      let circleCenter = CGPoint(x: circleView.frame.width / 2, y: circleView.frame.height / 2)
    let circleTrackPath = UIBezierPath(ovalIn:
    CGRect(origin: circleCenter, size: .zero)
    .insetBy(dx: circleRadius, dy: circleRadius))
    // smaller circle
    circleTrackPath.append(CGRect(origin: circleCenter, size: .zero)
    .insetBy(dx: circleRadius * 0.8, dy: circleRadius * 0.8))
  • Set circleTrackPath.usesEvenOddFillRule to true:

      circleTrackPath.usesEvenOddFillRule = true

Now you have a blurred full circle. The non-blurred arc part can be implemented as another sublayer.

Here is a MCVE that you can paste into a playground:

let container = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))

// change this to a view of your choice
let image = UIImageView(image: UIImage(named: "my_image"))
let blur = UIVisualEffectView(effect: UIBlurEffect(style: .light))
container.addSubview(image)
blur.frame = image.frame
container.addSubview(blur)
let outer = image.bounds.insetBy(dx: 30, dy: 30)
let path = UIBezierPath(ovalIn: outer)
path.usesEvenOddFillRule = true
path.append(UIBezierPath(ovalIn: outer.insetBy(dx: 10, dy: 10)))

let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
maskLayer.fillRule = .evenOdd
blur.layer.mask = maskLayer
container // <--- playground quick look this

Using my profile pic as the background, this produces:

Sample Image



Related Topics



Leave a reply



Submit