【问题标题】:Masked UIVisualEffectView does not work on iOS 10蒙面的 UIVisualEffectView 在 iOS 10 上不起作用
【发布时间】:2017-01-24 08:06:37
【问题描述】:

我创建了一个使用 UIBlurEffectView 的应用程序,该应用程序在 iOS 9 及更低版本上运行良好,但是当我升级我的设备(其中一些,而不仅仅是 1 台设备)时,模糊消失了,而不是模糊由于某种原因,一个半透明的视图。

这门课有什么变化吗?有人知道为什么吗?

我的代码(视图是我使用 PocketSVG API 获得的 SVG 文件的形状):

 let blur: UIBlurEffect = UIBlurEffect(style: .Light)
let ev: UIVisualEffectView = UIVisualEffectView(effect: blur)
ev.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(ev)

ev.rightAnchor.constraintEqualToAnchor(self.rightAnchor).active = true
ev.bottomAnchor.constraintEqualToAnchor(self.bottomAnchor).active = true
ev.leftAnchor.constraintEqualToAnchor(self.leftAnchor).active = true
ev.heightAnchor.constraintEqualToAnchor(self.heightAnchor, multiplier: 1.5).active = true

let myPath: CGPathRef = PocketSVG.pathFromSVGFileNamed("CategoriesBar").takeUnretainedValue()

var transform: CGAffineTransform = CGAffineTransformMakeScale(self.frame.size.width / 754.0, self.frame.size.height / 220.0)

let transformedPath: CGPathRef = CGPathCreateMutableCopyByTransformingPath(myPath, &transform)!

let myShapeLayer = CAShapeLayer()
myShapeLayer.path = transformedPath
self.layer.mask = myShapeLayer

Leo Natan 的答案代码:

你的建议不起作用,这是代码

 override func layoutSubviews() {

        let blur: UIBlurEffect = UIBlurEffect(style: .Light)
        let ev: UIVisualEffectView = UIVisualEffectView(effect: blur)
        ev.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(ev)

        ev.rightAnchor.constraintEqualToAnchor(self.rightAnchor).active = true
        ev.bottomAnchor.constraintEqualToAnchor(self.bottomAnchor).active = true
        ev.leftAnchor.constraintEqualToAnchor(self.leftAnchor).active = true
        ev.heightAnchor.constraintEqualToAnchor(self.heightAnchor, multiplier: 1.5).active = true

        let myPath: CGPathRef = PocketSVG.pathFromSVGFileNamed("CategoriesBar").takeUnretainedValue()

        var transform: CGAffineTransform = CGAffineTransformMakeScale(self.frame.size.width / 754.0, self.frame.size.height / 220.0)

        let transformedPath: CGPathRef = CGPathCreateMutableCopyByTransformingPath(myPath, &transform)!

        let myShapeLayer = CAShapeLayer()
        myShapeLayer.path = transformedPath
        self.layer.mask = myShapeLayer

        let myMaskedView = UIView(frame: ev.frame)
        myMaskedView.layer.mask = myShapeLayer
        ev.maskView = myMaskedView
}

Konrad Siemczyk 答案代码

override func layoutSubviews() {

        let blur: UIBlurEffect = UIBlurEffect(style: .Light)
        let ev: UIVisualEffectView = UIVisualEffectView(effect: blur)
        ev.frame = self.bounds
        ev.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(ev)

        ev.rightAnchor.constraintEqualToAnchor(self.rightAnchor).active = true
        ev.bottomAnchor.constraintEqualToAnchor(self.bottomAnchor).active = true
        ev.leftAnchor.constraintEqualToAnchor(self.leftAnchor).active = true
        ev.heightAnchor.constraintEqualToAnchor(self.heightAnchor, multiplier: 1.5).active = true

        let myPath: CGPathRef = PocketSVG.pathFromSVGFileNamed("CategoriesBar").takeUnretainedValue()
        var transform: CGAffineTransform = CGAffineTransformMakeScale(self.frame.size.width / 754.0, self.frame.size.height / 220.0)

        let transformedPath: CGPathRef = CGPathCreateMutableCopyByTransformingPath(myPath, &transform)!

        let myShapeLayer = CAShapeLayer()
        myShapeLayer.path = transformedPath
        //self.layer.mask = myShapeLayer
        myShapeLayer.fillRule = kCAFillRuleEvenOdd

        let myMaskedView = UIView(frame: self.frame)
        myMaskedView.backgroundColor = UIColor.blackColor()
        myMaskedView.layer.mask = myShapeLayer
        ev.maskView = myMaskedView
    }

【问题讨论】:

  • 可以给一些设备参考吗?
  • @lee5783 它在每台设备上(我在 2 个 iPhone 6S 和 1 个 iPhone 7 模拟器上都试过),有什么想法吗?
  • 我也有一个使用 UIBlurEffectView 的应用程序,但它在我的设备 iPad Air 上运行良好。我正在使用 Xcode 8 和 swift 2.3
  • @lee5783 可能是因为蒙版+模糊?
  • @FS.06:我使用自定义视图作为模糊视图

标签: ios iphone swift cocoa-touch uiview


【解决方案1】:

嘿,在实现这个之前......

TLDR:请先检查此解决方案:

https://*.com/a/67939549/2829540

...尽管这些示例适用于旧版本的 iOS,但看起来较新的示例需要图层而不是视图,但此答案可能无法按预期工作。您可能需要为旧版本实施此解决方案,而为较新版本实施链接的解决方案。


面向 ObjectiveC 用户。

这是一个适用于 iOS 10 的工作示例。 我还在最后附上了结果视图。稍后我将在顶部添加白色边框。裁剪圆掩码在代码中,如果您喜欢它,请按原样使用。

// "self" in here is an UIView that contains some images inside.
{
  UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
  UIVisualEffectView *blurredEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];

  CGRect frame = self.frame;
  frame.origin = CGPointMake (0, 0);

  blurredEffectView.frame = frame;
  [self addSubview:blurredEffectView];

  UIView *maskView = [[UIView alloc] initWithFrame:frame];
  maskView.backgroundColor = [UIColor blackColor];

  __weak UIView *weak = self;
  maskView.layer.mask = ({ // This mask draws a rectangle and crops a circle inside it.
    __strong UIView *strong = weak;

    CGRect roundedRect = CGRectMake (
      0,
      0,
      strong.frame.size.width * 0.8f,
      strong.frame.size.width * 0.8f
    );
    roundedRect.origin.x = strong.frame.size.width / 2 - roundedRect.size.width / 2;
    roundedRect.origin.y = strong.frame.size.height / 2 - roundedRect.size.height / 2;

    CGFloat cornerRadius = roundedRect.size.height / 2.0f;

    UIBezierPath *path        = [UIBezierPath bezierPathWithRect:self.bounds];
    UIBezierPath *croppedPath = [UIBezierPath bezierPathWithRoundedRect:roundedRect cornerRadius:cornerRadius];
    [path appendPath:croppedPath];
    [path setUsesEvenOddFillRule:YES];

    CAShapeLayer *mask = [CAShapeLayer layer];
    mask.path     = path.CGPath;
    mask.fillRule = kCAFillRuleEvenOdd;
    mask;
  });

  blurredEffectView.maskView = maskView;
}

所以,这与 Swift 3 相同的代码用于在 Playground 中进行测试。

这是在下载url时使用try,所以是同步的

import UIKit
import PlaygroundSupport

let generalFrame = CGRect(x: 0, y: 0, width: 500, height: 500)

let containerView = UIView(frame: generalFrame)
containerView.backgroundColor = UIColor.black;
PlaygroundPage.current.liveView = containerView

let parentView = UIView(frame: generalFrame)
containerView.addSubview(parentView)

let url = URL(string: "https://static.pexels.com/photos/168066/pexels-photo-168066-large.jpeg")
let data = try Data(contentsOf: url!);

let imageView = UIImageView(frame:parentView.bounds)
imageView.image = UIImage(data: data)
imageView.contentMode = .scaleAspectFill

let maskView = UIView(frame:parentView.bounds)
maskView.backgroundColor = UIColor.black
maskView.layer.mask = {() -> CALayer in
    var  roundedRect = CGRect (
        x: 0.0,
        y: 0.0,
        width: parentView.bounds.size.width * 0.5,
        height: parentView.bounds.size.width * 0.5
    );
    roundedRect.origin.x = parentView.frame.size.width / 2 - roundedRect.size.width / 2;
    roundedRect.origin.y = parentView.frame.size.height / 2 - roundedRect.size.height / 2;
    
    let cornerRadius = roundedRect.size.height / 2.0;
    
    let path = UIBezierPath(rect:parentView.bounds)
    let croppedPath = UIBezierPath(roundedRect: roundedRect, cornerRadius: cornerRadius)
    path.append(croppedPath)
    path.usesEvenOddFillRule = true
    
    let maskLayer = CAShapeLayer()
    maskLayer.path = path.cgPath;
    maskLayer.fillRule = kCAFillRuleEvenOdd
    return maskLayer
}()

let blurView = UIBlurEffect(style: .light)
let effectView = UIVisualEffectView(effect: blurView)
effectView.frame = generalFrame

effectView.mask = maskView
parentView.addSubview(imageView)
parentView.addSubview(effectView)

以及视图控制器中的工作示例:

这个先下载一张图片,然后添加模糊效果。

import UIKit

class ViewController: UIViewController {

  func addTheBlurView(data :Data) {

    let generalFrame = self.view.bounds;
    let parentView = UIView(frame: generalFrame)
    self.view.addSubview(parentView)

    let imageView = UIImageView(frame: parentView.bounds)
    imageView.image = UIImage(data: data)
    imageView.contentMode = .scaleAspectFill

    let maskView = UIView(frame: parentView.bounds)
    maskView.backgroundColor = UIColor.black
    maskView.layer.mask = {
      () -> CALayer in
      var roundedRect = CGRect(
          x: 0.0,
          y: 0.0,
          width: parentView.bounds.size.width * 0.5,
          height: parentView.bounds.size.width * 0.5
          );
      roundedRect.origin.x = parentView.frame.size.width / 2 - roundedRect.size.width / 2;
      roundedRect.origin.y = parentView.frame.size.height / 2 - roundedRect.size.height / 2;

      let cornerRadius = roundedRect.size.height / 2.0;

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

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

    let blurView = UIBlurEffect(style: .light)
    let effectView = UIVisualEffectView(effect: blurView)
    effectView.frame = generalFrame

    effectView.mask = maskView
    parentView.addSubview(imageView)
    parentView.addSubview(effectView)

  }

  override func viewDidLoad() {
    debugPrint("Running...")

    super.viewDidLayoutSubviews();

    // Lets load an image first, so blur looks cool
    let url = URL(string: "https://static.pexels.com/photos/168066/pexels-photo-168066-large.jpeg")

    URLSession.shared.dataTask(with: url!) {
      (data, response, error) in

      if error != nil {
        print(error)
        return
      }

      DispatchQueue.main.async(execute: {
        self.addTheBlurView(data: data!)
      })

    }.resume()

  }
}

目标版本

游乐场版

视图控制器版本

【讨论】:

  • 对我不起作用...你能根据我的代码检查一下并告诉我有什么问题吗?
  • 这段代码是objc,我想你正在使用swift?如果你复制粘贴这个,它对你不起作用。顺便说一句,你的代码在哪里?
  • 我已经用 Swift 重写了你的代码,我没有复制粘贴它:)。抱歉,我忘了在问题上添加我的代码;),请在 10 秒内刷新此页面。谢谢:)
  • 首先你没有为你的 UIVisualEffectView 设置一个框架,将它设置为 self.bounds 看看这是否能解决问题......其次,尝试给你的 myMaskedView 设置黑色的背景色......
  • @emrahgunduz 谢谢。我们最终找到了解决方案。检查下面的答案:*.com/a/67939549/2829540。我们从那个答案中得到了提示:*.com/a/39597884/2829540
【解决方案2】:

如果您正在 Swift 3.0 中寻找蒙版视图和模糊的解决方案,请查看以下代码:

class func addBlurredView(_ forView: UIView, centeredElement: UIView) {
    let blur = UIVisualEffectView(effect: UIBlurEffect(style: .light))
    blur.frame = forView.frame
    blur.isUserInteractionEnabled = false
    forView.addSubview(blur)

    let radius = centeredElement.bounds.width / 2
    let path = UIBezierPath (
        roundedRect: blur.frame,
        cornerRadius: 0)
    let circle = UIBezierPath (
        roundedRect: CGRect(origin: CGPoint(x: centeredElement.frame.origin.x, y: centeredElement.frame.origin.y),
                             size: centeredElement.frame.size), cornerRadius: radius)
    path.append(circle)
    path.usesEvenOddFillRule = true

    let maskLayer = CAShapeLayer()
    maskLayer.path = path.cgPath
    maskLayer.fillRule = kCAFillRuleEvenOdd

    let maskView = UIView(frame: forView.frame)
    maskView.backgroundColor = UIColor.black
    maskView.layer.mask = maskLayer

    blur.mask = maskView
}

【讨论】:

  • 对我不起作用... :/ 用我的代码更新了我的问题到你的答案
【解决方案3】:

感谢@emrahgunduz 的回答,我设法将上述代码更新为 Swift 5.2。

import UIKit
import PlaygroundSupport

let generalFrame = CGRect(x: 0, y: 0, width: 500, height: 500)
let containerView = UIView(frame: generalFrame)
containerView.backgroundColor = UIColor.black;

PlaygroundPage.current.liveView = containerView

let parentView = UIView(frame: generalFrame)
containerView.addSubview(parentView)

let url = URL(string: "https://static.pexels.com/photos/168066/pexels-photo-168066-large.jpeg")
let data = try Data(contentsOf: url!);

let imageView = UIImageView(frame:parentView.bounds)
imageView.image = UIImage(data: data)
imageView.contentMode = .scaleAspectFill
var  roundedRect = CGRect (
    x: 0.0,
    y: 0.0,
    width: parentView.bounds.size.width * 0.5,
    height: parentView.bounds.size.width * 0.5
);
roundedRect.origin.x = parentView.frame.size.width / 2 - roundedRect.size.width / 2;
roundedRect.origin.y = parentView.frame.size.height / 2 - roundedRect.size.height / 2;
let cornerRadius = roundedRect.size.height / 2.0;
let path = UIBezierPath(rect:parentView.bounds)
let croppedPath = UIBezierPath(roundedRect: roundedRect, cornerRadius: cornerRadius)
path.append(croppedPath)
path.usesEvenOddFillRule = true

let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath;
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
let blurView = UIBlurEffect(style: .light)
let effectView = UIVisualEffectView(effect: blurView)

effectView.frame = generalFrame
effectView.layer.mask = maskLayer

parentView.addSubview(imageView)
parentView.addSubview(effectView)

【讨论】:

    【解决方案4】:

    根据a discussion with Apple engineer,这是对UIVisualEffectView 工作方式的限制。它以前可以工作,但UIVisualEffectView 不太准确。

    讨论中建议的方法是使用maskView,而不是直接屏蔽图层。所以尝试创建一个视图,屏蔽该视图的图层,并将其设置为屏蔽视图。

    let myMaskedView = UIView(frame: ev.frame)
    myMaskedView.layer.mask = myShapeLayer
    ev.maskView = myMaskedView
    

    【讨论】:

    • 我不是很了解你,你能根据我上面发布的代码添加一个代码示例吗?非常感谢!
    • 我测试过,可以确认这适用于蒙版视图。
    • 这对我不起作用,我已经用你的回答用新代码更新了我的问题,你能检查一下并告诉我是否遗漏了什么吗?谢谢
    • 您的问题可能是由于没有为蒙版视图提供正确的框架。您正在使用自动布局,因此您可能需要先计算目标大小,或者在布局传递完成后在viewDidLayoutSubviews 中设置遮罩视图。请注意,如果您需要调整视觉效果视图(和蒙版)的大小,则需要在调整大小后再次设置maskView 属性。
    • 我确认它适用于 iOS 8 和 iOS 10,但不适用于 iOS 9(在 iOS 9.3.5 上的设备和模拟器上测试)
    【解决方案5】:

    此代码经过测试,可在 Ipad Air 2 模拟器 iOS 10.0 上运行

    class BlurView: UIView {
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.initialView()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            self.initialView()
        }
    
        func initialView() {
            if UIAccessibilityIsReduceTransparencyEnabled() == false {
                self.backgroundColor = UIColor.clearColor()
                let blurEffect = UIBlurEffect(style: .Dark)
    
                let blurEffectView = UIVisualEffectView(effect: blurEffect)
                blurEffectView.frame = self.bounds
                blurEffectView.translatesAutoresizingMaskIntoConstraints = true
                blurEffectView.autoresizingMask = UIViewAutoresizing.FlexibleWidth.union(.FlexibleHeight)
    
                self.addSubview(blurEffectView)
            } else {
                self.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.5)
            }
        }
    }
    

    您可以在情节提要上使用它或使用代码创建它。

    【讨论】:

    • 所以唯一的方法是使用第 3 方 API 而不是 UIVisualEffectView?
    • iPad Retina 已禁用模糊效果,就像 iPhone 4 和 iPad 2 一样。在 iPad Air 及更高版本的模拟器上运行您的代码,它将按预期工作。 iPad Retina 也不会收到 iOS 10 更新,所以也和这里的问题无关。
    • @LeoNatan 我在 iPhone 6S 上运行它还是不行
    • @FS.O6:我更新了我的答案,您可以在情节提要或代码中使用它
    • @lee5783 那只是给我一个半黑的视图(self.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.5)
    最近更新 更多