【问题标题】:Why does UIGraphicsGetCurrentContext return nil after UIGraphicsBeginImageContext为什么 UIGraphicsGetCurrentContext 在 UIGraphicsBeginImageContext 之后返回 nil
【发布时间】:2021-01-19 21:24:26
【问题描述】:

我正在按照代码示例制作模糊的 UILabel,https://stackoverflow.com/a/62224908/2226315

我的要求是在标签初始化后使标签模糊,而不是在运行时调用blur 方法。但是,当我在标签初始化后尝试调用blur 时,从UIGraphicsGetCurrentContext 返回的值是nil,因此出现“致命错误:在展开可选值时意外发现nil”

UIGraphicsBeginImageContext(bounds.size)
print("DEBUG: bounds.size", bounds.size)
self.layer.render(in: UIGraphicsGetCurrentContext()!) // <- return nil
var image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
print("DEBUG: image image", image)

我尝试在以下所有位置分别添加代码,现在可以获取上下文但是它不会产生预期的模糊效果。

override func layoutSubviews() {
    super.layoutSubviews()
    self.blur()
}

// OR
    
override func draw(_ rect: CGRect) {
    super.draw(rect)
    self.blur()
}

全码sn-p,

class BlurredLabel: UILabel {

    func blur(_ blurRadius: Double = 2.5) {        
        let blurredImage = getBlurryImage(blurRadius)
        let blurredImageView = UIImageView(image: blurredImage)
        blurredImageView.translatesAutoresizingMaskIntoConstraints = false
        blurredImageView.tag = 100
        blurredImageView.contentMode = .center
        blurredImageView.backgroundColor = .white
        addSubview(blurredImageView)
        NSLayoutConstraint.activate([
            blurredImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
            blurredImageView.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])
    }

    func unblur() {
        subviews.forEach { subview in
            if subview.tag == 100 {
                subview.removeFromSuperview()
            }
        }
    }

    private func getBlurryImage(_ blurRadius: Double = 2.5) -> UIImage? {
        UIGraphicsBeginImageContext(bounds.size)
        layer.render(in: UIGraphicsGetCurrentContext()!)
        guard let image = UIGraphicsGetImageFromCurrentImageContext(),
            let blurFilter = CIFilter(name: "CIGaussianBlur") else {
            UIGraphicsEndImageContext()
            return nil
        }
        UIGraphicsEndImageContext()

        blurFilter.setDefaults()

        blurFilter.setValue(CIImage(image: image), forKey: kCIInputImageKey)
        blurFilter.setValue(blurRadius, forKey: kCIInputRadiusKey)

        var convertedImage: UIImage?
        let context = CIContext(options: nil)
        if let blurOutputImage = blurFilter.outputImage,
            let cgImage = context.createCGImage(blurOutputImage, from: blurOutputImage.extent) {
            convertedImage = UIImage(cgImage: cgImage)
        }

        return convertedImage
    }
}

参考

更新

基于“Eugene Dudnyk”答案的用法


definitionLabel = BlurredLabel()
definitionLabel.numberOfLines = 0
definitionLabel.lineBreakMode = .byWordWrapping
definitionLabel.textColor = UIColor(named: "text")
definitionLabel.text = "Lorem Ipsum is simply dummy text"
definitionLabel.clipsToBounds = false
definitionLabel.isBluring = true

【问题讨论】:

  • 你检查过CGRectIsEmpty(bounds) == false吗?
  • 一种更简单的方法可能是将 UIVisualEffectView 添加为标签的子视图,并在需要时将其隐藏。您可以将 UIBlurEffect 附加到 UIVisualEffectView 以获得所需的效果
  • @Pastre 我试图将标签放在 UIVisualEffectView 后面,但它看起来不如通过 CoreImage 创建的模糊效果。
  • @EugeneDudnyk 我刚刚检查了边界,标签初始化后它是空的。您能否与我分享一下获取上下文的正确位置?
  • 如果边界是空的,你甚至不应该画任何东西。在这种情况下,Label 不会在屏幕上占据任何空间,稍后它将获得非空边界。只是在获得边界之前不要创建上下文。

标签: ios swift uikit core-image


【解决方案1】:

这是一个更好的解决方案 - 不是检索模糊的图像,而是让标签本身模糊。

当你需要模糊时,设置label.isBlurring = true。 此外,此解决方案的性能更好,因为它重用了相同的上下文并且不需要图像视图。

class BlurredLabel: UILabel {
    
    var isBlurring = false {
        didSet {
            setNeedsDisplay()
        }
    }

    var blurRadius: Double = 2.5 {
        didSet {
            blurFilter?.setValue(blurRadius, forKey: kCIInputRadiusKey)
        }
    }

    lazy var blurFilter: CIFilter? = {
        let blurFilter = CIFilter(name: "CIGaussianBlur")
        blurFilter?.setDefaults()
        blurFilter?.setValue(blurRadius, forKey: kCIInputRadiusKey)
        return blurFilter
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        layer.isOpaque = false
        layer.needsDisplayOnBoundsChange = true
        layer.contentsScale = UIScreen.main.scale
        layer.contentsGravity = .center
        isOpaque = false
        isUserInteractionEnabled = false
        contentMode = .redraw
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func display(_ layer: CALayer) {
        let bounds = layer.bounds
        guard !bounds.isEmpty && bounds.size.width < CGFloat(UINT16_MAX) else {
            layer.contents = nil
            return
        }
        UIGraphicsBeginImageContextWithOptions(layer.bounds.size, layer.isOpaque, layer.contentsScale)
        if let ctx = UIGraphicsGetCurrentContext() {
            self.layer.draw(in: ctx)
        
            var image = UIGraphicsGetImageFromCurrentImageContext()?.cgImage
            if isBlurring, let cgImage = image {
                blurFilter?.setValue(CIImage(cgImage: cgImage), forKey: kCIInputImageKey)
                let ciContext = CIContext(cgContext: ctx, options: nil)
                if let blurOutputImage = blurFilter?.outputImage,
                   let cgImage = ciContext.createCGImage(blurOutputImage, from: blurOutputImage.extent) {
                    image = cgImage
                }
            }
            layer.contents = image
        }
        UIGraphicsEndImageContext()
    }
}

【讨论】:

  • 先生,对于上面的代码,您对 UITextView 有什么建议吗?
  • @KishanBhatiya 如果您仅使用文本视图来显示链接,则可以使用 BlurredLabel 代替,并将 attributedText 设置为它,就像这里描述的那样 stackoverflow.com/questions/1256887/…
  • 感谢您的快速回复,使用文本视图我没有显示任何链接,而是显示属性字符串。我们如何用文本视图做到这一点?你可以check this
  • @KishanBhatiya 文本视图使用私有子视图来显示文本,这就是您的场景中应该模糊的视图。我不认为你可以不使用私有 api 来实现它
  • 我可以使用VisualEffectView(),但可以使用整个文本视图,而不是文本视图的文本,并且它还显示边缘,正如您在评论中提到的我的问题中看到的那样。您对VisualEffectView() 有什么建议或想法会有所帮助
猜你喜欢
  • 2012-09-07
  • 2019-02-18
  • 2014-11-11
  • 2021-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多