You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 20:00:54