2

I was making a progress circle, and I want its track path to have a blur effect, is there any way to achieve that?

This is what the original track looks like(the track path is transparent, I want it to be blurred)

enter image description here

And this is my attempt
And this is my attempt

let outPutViewFrame = CGRect(x: 0, y: 0, width: 500, height: 500)
let circleRadius: CGFloat = 60
let circleViewCenter = CGPoint(x: outPutViewFrame.width / 2 , y: outPutViewFrame.height / 2)
let circleView = UIView()
let progressWidth: CGFloat = 8

circleView.frame.size = CGSize(width: (circleRadius + progressWidth) * 2, height: (circleRadius + progressWidth) * 2)
circleView.center = circleViewCenter


let circleTrackPath = UIBezierPath(arcCenter: CGPoint(x: circleView.frame.width / 2, y: circleView.frame.height / 2), radius: circleRadius, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
let blur = UIBlurEffect(style: .light)
let blurEffect = UIVisualEffectView(effect: blur)
blurEffect.frame = circleView.bounds
blurEffect.mask(withPath: circleTrackPath, inverse: false)

extension UIView {

    func mask(withPath path: UIBezierPath, inverse: Bool = false) {
    
        let maskLayer = CAShapeLayer()

        if inverse {
            path.append(UIBezierPath(rect: self.bounds))
            maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
        }
        
        maskLayer.path = path.cgPath
        maskLayer.lineWidth = 5
        maskLayer.lineCap = CAShapeLayerLineCap.round
       

        self.layer.mask = maskLayer
    }
}
Zim
  • 674
  • 7
  • 15
  • What do you mean you want your track path to have a blur effect? I don't see any blurring in your sample image. The circle appears to be drawing in partly transparent blue on top of your source image. – Duncan C Aug 12 '21 at 02:58

1 Answers1

1
  • 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:

enter image description here

Sweeper
  • 213,210
  • 22
  • 193
  • 313