Tại sao UIGraphicsGetCurrentContext trả về con số không sau UIGraphicsBeginImageContext

Oct 04 2020

Tôi đang làm theo một ví dụ mã để tạo một UILabel mờ, https://stackoverflow.com/a/62224908/2226315.

Yêu cầu của tôi là làm cho nhãn mờ sau khi khởi tạo nhãn thay vì gọi blurphương thức trong thời gian chạy. Tuy nhiên, khi tôi cố gắng gọi blursau khi nhãn được khởi tạo giá trị trả về từ UIGraphicsGetCurrentContextđược nilvì có một "Fatal error: Thật bất ngờ tìm thấy con số không trong khi unwrapping một giá trị bắt buộc"

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)

Tôi đã thử thêm mã vào tất cả các vị trí sau riêng lẻ, ngữ cảnh có thể được tìm nạp ngay bây giờ tuy nhiên nó không tạo ra hiệu ứng mờ như mong đợi.

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

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

Đoạn mã đầy đủ,

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

TÀI LIỆU THAM KHẢO

  • Thêm chế độ xem mờ vào nhãn?
  • Cách làm mờ văn bản UILabel

CẬP NHẬT

Cách sử dụng dựa trên câu trả lời "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

Trả lời

2 EugeneDudnyk Oct 05 2020 at 03:16

Đây là một giải pháp tốt hơn - thay vì lấy lại hình ảnh bị mờ, chỉ cần để nhãn tự làm mờ.

Khi bạn cần nó được làm mờ, hãy đặt label.isBluring = true. Ngoài ra, giải pháp này tốt hơn cho hiệu suất, vì nó sử dụng lại cùng ngữ cảnh và không cần chế độ xem hình ảnh.

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