为什么在 UIGraphicsBeginImageContext 之后 UIGraphicsGetCurrentContext return nil
Why does UIGraphicsGetCurrentContext return nil after UIGraphicsBeginImageContext
我正在按照代码示例制作模糊的 UILabel,。
我的要求是在标签初始化后使标签模糊,而不是在运行时调用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()
}
完整代码片段,
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
这里有一个更好的解决方案 - 不是检索模糊图像,而是让标签本身模糊。
需要模糊时设置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()
}
}
我正在按照代码示例制作模糊的 UILabel,
我的要求是在标签初始化后使标签模糊,而不是在运行时调用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()
}
完整代码片段,
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
这里有一个更好的解决方案 - 不是检索模糊图像,而是让标签本身模糊。
需要模糊时设置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()
}
}