Pourquoi UIGraphicsGetCurrentContext renvoie nil après UIGraphicsBeginImageContext

Oct 04 2020

Je suis un exemple de code pour créer un UILabel flou, https://stackoverflow.com/a/62224908/2226315.

Mon exigence est de rendre l'étiquette floue après l'initialisation de l'étiquette au lieu d'appeler la blurméthode au moment de l'exécution. Cependant, quand je tente d'appeler bluraprès l' étiquette s'initialisé la valeur renvoyée par UIGraphicsGetCurrentContextest par nilconséquent avoir une « erreur fatale: néant tout trouvé de façon inattendue déballant une valeur facultative »

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)

J'ai essayé d'ajouter le code dans tous les endroits suivants individuellement, le contexte peut être récupéré maintenant mais il ne génère pas l'effet de flou comme prévu.

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

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

Extrait de code complet,

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
    }
}

RÉFÉRENCE

  • Ajouter une vue floue à l'étiquette?
  • Comment flouter le texte UILabel

METTRE À JOUR

Utilisation basée sur la réponse "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

Réponses

2 EugeneDudnyk Oct 05 2020 at 03:16

Voici une meilleure solution - au lieu de récupérer l'image floue, laissez simplement l'étiquette se brouiller.

Lorsque vous en avez besoin pour être flou, réglez label.isBluring = true. En outre, cette solution est meilleure pour les performances, car elle réutilise le même contexte et n'a pas besoin de la vue d'image.

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()
    }
}