在 UIBlurEffect 上画洞

Draw hole on UIBlurEffect

Xcode 8.0 - Swift 2.3
我有一个内部扩展来创建效果很好的模糊层:

internal extension UIView {
    
    /**
     Add and display on current view a blur effect.
     */
    internal func addBlurEffect(style style: UIBlurEffectStyle = .ExtraLight, atPosition position: Int = -1) -> UIView {
        // Blur Effect
        let blurEffectView = self.createBlurEffect(style: style)
        if position >= 0 {
            self.insertSubview(blurEffectView, atIndex: position)
        } else {
            self.addSubview(blurEffectView)
        }
        return blurEffectView
    }
 
    internal func createBlurEffect(style style: UIBlurEffectStyle = .ExtraLight) -> UIView {
        let blurEffect = UIBlurEffect(style: style)
        let blurEffectView = UIVisualEffectView(effect: blurEffect)
        blurEffectView.frame = self.bounds
        blurEffectView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        return blurEffectView
    }
    
}

问题是:如何在模糊叠加层中添加异形孔? 我做了很多尝试:

let p = UIBezierPath(roundedRect: CGRectMake(0.0, 0.0, self.viewBlur!.frame.width, self.viewBlur!.frame.height), cornerRadius: 0.0)
p.usesEvenOddFillRule = true
let f = CAShapeLayer()
f.fillColor = UIColor.redColor().CGColor
f.opacity = 0.5
f.fillRule = kCAFillRuleEvenOdd
p.appendPath(self.holePath)
f.path = p.CGPath
self.viewBlur!.layer.addSublayer(f)

但结果是:

我不明白为什么孔在 UIVisualEffectView 上可以,但在 _UIVisualEffectBackdropView

上不行

更新

我试过@Arun 的解决方案(UIBlurEffectStyle.Dark),但结果不一样:

更新 2

使用@Dim_ov的解决方案我有:

为了完成这项工作,我需要以这种方式隐藏_UIVisualEffectBackdropView

    for v in effect.subviews {
        if let filterView = NSClassFromString("_UIVisualEffectBackdropView") {
            if v.isKindOfClass(filterView) {
                v.hidden = true
            }
        }
    }

请在此处检查我的代码

内部扩展 UIView {

/**
 Add and display on current view a blur effect.
 */
internal func addBlurEffect(style style: UIBlurEffectStyle = .ExtraLight, atPosition position: Int = -1) -> UIView {
    // Blur Effect
    let blurEffectView = self.createBlurEffect(style: style)
    if position >= 0 {
        self.insertSubview(blurEffectView, atIndex: position)
    } else {
        self.addSubview(blurEffectView)
    }
    return blurEffectView
}

internal func createBlurEffect(style style: UIBlurEffectStyle = .ExtraLight) -> UIView {
    let blurEffect = UIBlurEffect(style: style)
    let blurEffectView = UIVisualEffectView(effect: blurEffect)
    blurEffectView.frame = self.bounds
    blurEffectView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
    return blurEffectView
}

}

class ViewController: UIViewController {

@IBOutlet weak var blurView: UIImageView!

var blurEffectView: UIView!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    blurEffectView = blurView.addBlurEffect(style: UIBlurEffectStyle.Light, atPosition: 0)


}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    let outerbezierPath = UIBezierPath.init(roundedRect: self.blurEffectView.frame, cornerRadius: 0)
    let rect = CGRectMake(150, 150, 100, 100)
    let innerCirclepath = UIBezierPath.init(roundedRect:rect, cornerRadius:rect.height * 0.5)
    outerbezierPath.appendPath(innerCirclepath)
    outerbezierPath.usesEvenOddFillRule = false
    let fillLayer = CAShapeLayer()
    fillLayer.fillRule = kCAFillRuleEvenOdd
    fillLayer.fillColor = UIColor.blackColor().CGColor
    fillLayer.path = outerbezierPath.CGPath
    self.blurEffectView.layer.mask = fillLayer
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

} 随着 UIBlurEffectStyle.Light

与UIBlurEffectStyle.Dark

与UIBlurEffectStyle.ExtraLight

在 iOS 10 中你必须使用 UIVisualEffectViewmask 属性 而不是 CALayermask.

我在 iOS 10 或 Xcode 8 的一些早期测试版的发行说明中看到了这一点,但我现在找不到那些说明 :)。我会在找到答案后立即用适当的 link 更新我的答案。

所以这是在 iOS 10/Xcode 8 中工作的代码:

class ViewController: UIViewController {
    @IBOutlet var blurView: UIVisualEffectView!

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        updateBlurViewHole()
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        updateBlurViewHole()
    }

    func updateBlurViewHole() {
        let maskView = UIView(frame: blurView.bounds)
        maskView.clipsToBounds = true;
        maskView.backgroundColor = UIColor.clear

        let outerbezierPath = UIBezierPath.init(roundedRect: blurView.bounds, cornerRadius: 0)
        let rect = CGRect(x: 150, y: 150, width: 100, height: 100)
        let innerCirclepath = UIBezierPath.init(roundedRect:rect, cornerRadius:rect.height * 0.5)
        outerbezierPath.append(innerCirclepath)
        outerbezierPath.usesEvenOddFillRule = true

        let fillLayer = CAShapeLayer()
        fillLayer.fillRule = kCAFillRuleEvenOdd
        fillLayer.fillColor = UIColor.green.cgColor // any opaque color would work
        fillLayer.path = outerbezierPath.cgPath
        maskView.layer.addSublayer(fillLayer)

        blurView.mask = maskView;
    }
}

Swift 2.3版本:

class ViewController: UIViewController {
    @IBOutlet var blurView: UIVisualEffectView!

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)

        updateBlurViewHole()
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        updateBlurViewHole()
    }

    func updateBlurViewHole() {
        let maskView = UIView(frame: blurView.bounds)
        maskView.clipsToBounds = true;
        maskView.backgroundColor = UIColor.clearColor()

        let outerbezierPath = UIBezierPath.init(roundedRect: blurView.bounds, cornerRadius: 0)
        let rect = CGRect(x: 150, y: 150, width: 100, height: 100)
        let innerCirclepath = UIBezierPath.init(roundedRect:rect, cornerRadius:rect.height * 0.5)
        outerbezierPath.appendPath(innerCirclepath)
        outerbezierPath.usesEvenOddFillRule = true

        let fillLayer = CAShapeLayer()
        fillLayer.fillRule = kCAFillRuleEvenOdd
        fillLayer.fillColor = UIColor.greenColor().CGColor
        fillLayer.path = outerbezierPath.CGPath
        maskView.layer.addSublayer(fillLayer)

        blurView.maskView = maskView
    }
}

更新

好吧,这是 Apple 开发者论坛的讨论,而不是 iOS 发行说明。但是有苹果代表的回答所以我觉得,这个信息可以考虑"official".

A link 讨论:https://forums.developer.apple.com/thread/50854#157782

Masking the layer of a visual effect view is not guaranteed to produce the correct results – in some cases on iOS 9 it would produce an effect that looked correct, but potentially sourced the wrong content. Visual effect view will no longer source the incorrect content, but the only supported way to mask the view is to either use cornerRadius directly on the visual effect view’s layer (which should produce the same result as you are attempting here) or to use the visual effect view’s maskView property.

我的代码中遇到了同样的问题。在我对包含 UIVisualEffectViewUIView 应用蒙版后,模糊消失了。但是,我可以通过覆盖父级上的 draw 方法来将其恢复:

override func draw(_ rect: CGRect) {
    super.draw(rect)
}

我不知道它为什么能正常工作,但希望它能对某人有所帮助。我用来挖洞的代码:

private func makeViewFinderMask() -> CAShapeLayer {

    let path = UIBezierPath(rect: bounds)
    let croppedPath = UIBezierPath(roundedRect: viewFinderBounds(), cornerRadius: viewFinderRadius)
    path.append(croppedPath)
    path.usesEvenOddFillRule = true

    let mask = CAShapeLayer()
    mask.path = path.cgPath
    mask.fillRule = kCAFillRuleEvenOdd
    return mask
}