iOS17更新后Widget中QR图像无法渲染及透明背景问题
iOS 17 Widget 二维码渲染与透明背景问题
问题描述
原本通过扩展方法将字符串转换为二维码图像,在iOS 17更新前可在Widget中正常渲染,但更新后二维码完全无法显示。调研后找到新方法能生成二维码,但无法设置透明背景;尝试将UIImage转换为CIImage处理透明效果后,图像又无法在Widget中渲染。
iOS 17之前使用的扩展代码
extension UIImage { convenience init?(qrcode string: String) { if let outputCIImage = CIImage.make(qrcode: string) { let context = CIContext() guard let cgImage = context.createCGImage(outputCIImage, from: outputCIImage.extent) else { return nil } self.init(cgImage: cgImage) return } return nil } convenience init?(qrcode string: String, using color: UIColor) { if let outputCIImage = CIImage.make(qrcode: string)?.tinted(using: color) { let context = CIContext() guard let cgImage = context.createCGImage(outputCIImage, from: outputCIImage.extent) else { return nil } self.init(cgImage: cgImage) return } return nil } } extension CIImage { static func make(qrcode string: String) -> CIImage? { let data = string.data(using: .ascii) if let filter = CIFilter(name: "CIQRCodeGenerator") { filter.setValue(data, forKey: "inputMessage") filter.setValue("Q", forKey: "inputCorrectionLevel") let transform = CGAffineTransform(scaleX: 12, y: 12) return filter.outputImage?.transformed(by: transform) } return nil } var transparent: CIImage? { inverted?.blackTransparent } var inverted: CIImage? { guard let invertedColorFilter = CIFilter(name: "CIColorInvert") else { return nil } invertedColorFilter.setValue(self, forKey: "inputImage") return invertedColorFilter.outputImage } var blackTransparent: CIImage? { guard let blackTransparentFilter = CIFilter(name: "CIMaskToAlpha") else { return nil } blackTransparentFilter.setValue(self, forKey: "inputImage") return blackTransparentFilter.outputImage } func tinted(using color: UIColor) -> CIImage? { guard let transparentQRImage = transparent, let filter = CIFilter(name: "CIMultiplyCompositing"), let colorFilter = CIFilter(name: "CIConstantColorGenerator") else { return nil } let ciColor = CIColor(color: color) colorFilter.setValue(ciColor, forKey: kCIInputColorKey) let colorImage = colorFilter.outputImage filter.setValue(colorImage, forKey: kCIInputImageKey) filter.setValue(transparentQRImage, forKey: kCIInputBackgroundImageKey) return filter.outputImage } func combined(with image: CIImage) -> CIImage? { guard let combinedFilter = CIFilter(name: "CISourceOverCompositing") else { return nil } let centerTransform = CGAffineTransform(translationX: extent.midX - (image.extent.size.width / 2), y: extent.midY - (image.extent.size.height / 2)) combinedFilter.setValue(image.transformed(by: centerTransform), forKey: "inputImage") combinedFilter.setValue(self, forKey: "inputBackgroundImage") return combinedFilter.outputImage! } }
调研后找到的新扩展方法
extension UIImage { static func make(qrcode string: String) -> UIImage? { guard let data = string.data(using: String.Encoding.ascii) else { return nil } var uiImage: UIImage? if let filter = CIFilter( name: "CIQRCodeGenerator", parameters: ["inputMessage": data, "inputCorrectionLevel": "Q"] ) { guard let outputImage = filter.outputImage, let cgImage = CIContext().createCGImage(outputImage, from: outputImage.extent) else { return nil } let scaleFactor: CGFloat = 12 let size = CGSize( width: outputImage.extent.width * scaleFactor, height: outputImage.extent.height * scaleFactor ) UIGraphicsBeginImageContext(size) if let context = UIGraphicsGetCurrentContext() { context.interpolationQuality = .none context.draw(cgImage, in: CGRect(origin: .zero, size: size)) uiImage = UIGraphicsGetImageFromCurrentImageContext() } UIGraphicsEndImageContext() } return uiImage } }
问题原因与解决方案
核心原因
iOS 17对Widget环境下的Core Image渲染逻辑做了调整:直接通过CIContext.createCGImage生成的带透明通道图像,在Widget中无法被正确解析渲染;而来回转换UIImage与CIImage会进一步触发兼容性问题。
修复方案
整合透明背景、着色与Widget兼容的渲染逻辑,直接在生成流程中完成所有处理,最终通过UIGraphicsBeginImageContextWithOptions生成兼容的UIImage:
extension UIImage { static func make(qrcode string: String, tintColor: UIColor? = nil) -> UIImage? { guard let data = string.data(using: .ascii) else { return nil } // 生成基础二维码CIImage guard let filter = CIFilter(name: "CIQRCodeGenerator", parameters: [ "inputMessage": data, "inputCorrectionLevel": "Q" ]), let outputImage = filter.outputImage else { return nil } var processedImage: CIImage = outputImage // 处理透明背景与着色 if let color = tintColor { // 转换为透明背景二维码 guard let inverted = processedImage.applyingFilter("CIColorInvert"), let transparentQR = inverted.applyingFilter("CIMaskToAlpha") else { return nil } // 生成纯色图层 guard let colorFilter = CIFilter(name: "CIConstantColorGenerator", parameters: [ kCIInputColorKey: CIColor(color: color) ]), let colorImage = colorFilter.outputImage else { return nil } // 混合颜色与透明二维码 guard let compositeFilter = CIFilter(name: "CIMultiplyCompositing", parameters: [ kCIInputImageKey: colorImage, kCIInputBackgroundImageKey: transparentQR ]), let tintedImage = compositeFilter.outputImage else { return nil } processedImage = tintedImage } else { // 无着色时直接生成透明背景二维码 if let inverted = processedImage.applyingFilter("CIColorInvert"), let transparentQR = inverted.applyingFilter("CIMaskToAlpha") { processedImage = transparentQR } } // 缩放并生成兼容Widget的UIImage let scaleFactor: CGFloat = 12 let size = CGSize( width: processedImage.extent.width * scaleFactor, height: processedImage.extent.height * scaleFactor ) // 创建带透明通道的上下文(第二个参数false表示透明背景) UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) defer { UIGraphicsEndImageContext() } // 确保上下文被正确释放 guard let context = UIGraphicsGetCurrentContext(), let cgImage = CIContext().createCGImage(processedImage, from: processedImage.extent) else { return nil } context.interpolationQuality = .none // 避免缩放模糊 context.draw(cgImage, in: CGRect(origin: .zero, size: size)) return UIGraphicsGetImageFromCurrentImageContext() } }
使用示例
// 生成默认黑色透明背景二维码 let qrImage = UIImage.make(qrcode: "your_string_here") // 生成蓝色透明背景二维码 let blueQRImage = UIImage.make(qrcode: "your_string_here", tintColor: .blue)
内容的提问来源于stack exchange,提问作者Sevenfortyseven
相关产品推荐
相关产品推荐

