iosswiftxcodewidgetqr-code

In iOS 17.4.1 widget QRCode is not rendering, before iOS 17 it was working fine


I'm experiencing an issue with a widget in my iOS app where a QRCode is not rendering correctly on iPhone 12 with iOS 17.4.1.

This functionality was working perfectly in all versions up to iOS 17. After updating to iOS 17.4.1, the QRCode no longer appears in the widget. The rest of the widget's content displays as expected, but the QRCode view is missing. I've confirmed that the QRCode generation code works fine within the app itself, and it only fails within the widget on the latest iOS version. I've also tried different approaches to ensure it's not a rendering issue, but nothing seems to work. Has anyone else experienced this issue with widgets on iOS 17.4.1, and is there a known workaround or fix for this problem?

I also checked that with Xcode 15.3, Simulator (17.4) it is working fine but in iPhone 12 (iOS 17.4.1) it's just showing App logo in QRCode Widget instead of QRCode.

Here with I attached some code necessary to reproduce the problem.

here is QRCodeSmallWidgetView code:

import SwiftUI
import WidgetKit

struct QRCodeSmallWidgetView: View {
    
    // Create the UserDefaults suites for Widget
    let appWidgetSuite = UserDefaults(suiteName: "group.zxc.asd.app.Abc-Widget")
    
    var body: some View {
        
        ZStack {
            
            // widget content
            if let userURL = appWidgetSuite?.string(forKey: "UserWidgetQRCode") {
                
                let imgQR = UIImage().generateQRCodeFromString(barcode: userURL)
                
                let qrImage = UIImage().convert(imgQR)
                
                let qrImageWithAppLogo = UIImage().addAppLogoToQRCodeInWidget(qrCodeImage: qrImage, logoImage: UIImage(named: "ic_qr_logo"))
                
                // Here Pass qrImage | qrImageWithAppLogo
                Image(uiImage: qrImageWithAppLogo)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            } else {
                Text("Add APP QR")
            }
        }
        .widgetBackground(Color(UIColor.systemBackground))
    }
}

struct QRCodeSmallWidgetView_Previews: PreviewProvider {
    static var previews: some View {
        QRCodeSmallWidgetView()
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}

extension View {
    func widgetBackground(_ backgroundView: some View) -> some View {
        if #available(iOSApplicationExtension 17.0, *) {
            return containerBackground(for: .widget) {
                backgroundView
            }
        } else {
            return background(backgroundView)
        }
    }
}

here is QRCodeWidgetEntryView code:

import SwiftUI
import WidgetKit

struct QRCodeWidgetEntryView: View {
    var entry: Provider.Entry

    @Environment(\.widgetFamily) var family

    @ViewBuilder
    var body: some View {
        switch family {
        case .systemSmall:
            QRCodeSmallWidgetView()

        default:
            fatalError()
        }
    }
}

here is App_WidgetBundle code:

import WidgetKit
import SwiftUI

@main
struct App_WidgetBundle: Widget {
    let kind: String = "App_WidgetBundle"

    var body: some WidgetConfiguration {
        
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            QRCodeWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("App QRCode Widget")
        .description("Instantly share your QRCode from your home screen.")
        .supportedFamilies([.systemSmall])
    }
}


here is Provider code:

import WidgetKit
import SwiftUI

struct Provider: TimelineProvider {
    typealias Entry = SimpleEntry

    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), qrImage: "staticQRCode")
    }

    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), qrImage: "staticQRCode")
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {

        let entry = SimpleEntry(date: Date(), qrImage: "")
        let timeline = Timeline(entries: [entry], policy: .atEnd)
        completion(timeline)
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
    let qrImage: String
}

here is UIImage extension code:

extension UIImage {
    
    func generateQRCodeFromString(barcode: String) -> CIImage {
        
        guard let dataString = barcode.data(using: .ascii) else {
            fatalError("Failed to convert string to data.")
        }
        
        guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else {
            fatalError("Failed to create CIFilter.")
        }
        
        qrFilter.setValue(dataString, forKey: "inputMessage")
        qrFilter.setValue("Q", forKey: "inputCorrectionLevel")
        
        guard let outputImage = qrFilter.outputImage else {
            fatalError("Failed to generate output image.")
        }
        
        let transform = CGAffineTransform(scaleX: 5.0, y: 5.0)
        return outputImage.transformed(by: transform)
    }
    
    func convert(_ cmage: CIImage) -> UIImage {
        
        let context: CIContext = CIContext.init(options: nil)
        let cgImage: CGImage = context.createCGImage(cmage, from: cmage.extent)!
        let image: UIImage = UIImage.init(cgImage: cgImage)
        return image
    }
    
    func addAppLogoToQRCodeInWidget(qrCodeImage: UIImage, logoImage: UIImage?) -> UIImage {
        
        let size = CGSize(width: qrCodeImage.size.width, height: qrCodeImage.size.height)
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        
        qrCodeImage.draw(in: CGRect(origin: CGPoint.zero, size: size))
        
        // Calculate the position to place the logo in the center
        let logoSize = CGSize(width: size.width * 0.48, height: size.height * 0.22)
        let origin = CGPoint(x: (size.width - logoSize.width) / 2, y: (size.height - logoSize.height) / 2)
        
        logoImage?.draw(in: CGRect(origin: origin, size: logoSize))
        
        let finalImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        
        return finalImage
    }
}

Solution

  • static func generateQRCodeFromString(barcode: String) -> UIImage? {
            
            guard let data = barcode.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
        }
    

    Actual issue was in image creation logic. Here I just change the above 2 functions which convert CIImage to UIImage as per the above and now it's working fine.