7

im trying to generate a QR code in my app. The problem is that whenever I do the picture is just an empty square. I stripped down the code to the basics to try and show my problem.

struct ContentView: View {
    @State var image: Image = Image(systemName: "circle.fill")

    var body: some View {
        VStack {
            image
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 200, height: 200)
                .background(Color.green)

        }.onAppear {
            let myString = "Hello There"
            let data = myString.data(using: String.Encoding.ascii)
            guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else { return }
            qrFilter.setValue(data, forKey: "inputMessage")
            guard let qrImage = qrFilter.outputImage else { return }
            let transform = CGAffineTransform(scaleX: 10, y: 10)
            let scaledQrImage = qrImage.transformed(by: transform)
            self.image = Image(uiImage: UIImage(ciImage: scaledQrImage))
        }
    }
}

The Result is this: Screenshot of Result

nOk
  • 2,725
  • 4
  • 13
  • 26
  • Possible duplicate of [SwiftUI: UIImage (QRCode) does not load after calling function](https://stackoverflow.com/questions/58172122/swiftui-uiimage-qrcode-does-not-load-after-calling-function) – LuLuGaGa Oct 04 '19 at 19:17

2 Answers2

21

I guess the problem is that your CIImage is not actually "produced". You see, a CIImage is just a recipe for an image that needs to be rendered by a CIContext into an actual bitmap image.

The (poorly documented) convenient initializer UIImage(ciImage:) only works if the destination you assign the image to understands that the pixels of the UIImage are not yet there and need to be rendered first. UIImageView could handle this, but it seems SwiftUI's Image doesn't.

What you need to do is to create a CIContext (once, maybe as a property of your view) and use it to render your barcode image into a bitmap like this:

let cgImage = self.ciContext.createCGImage(scaledQrImage, from: scaledQrImage.extent)
self.image = Image(uiImage: UIImage(cgImage: cgImage))
Frank Rupprecht
  • 9,191
  • 31
  • 56
3
func returnQRData(str: String) -> Data {
     let filter = CIFilter(name: "CIQRCodeGenerator")
     let data = str.data(using: .ascii, allowLossyConversion: false)
     filter?.setValue(data, forKey: "inputMessage")
     let transform = CGAffineTransform(scaleX: 5, y: 5)


     let image = filter?.outputImage?.transformed(by: transform)
     let uiImage = UIImage(ciImage: image!)

     return uiImage.pngData()!
}

Image(uiImage: UIImage(data: self.returnQRData(str: "www.apple.com")) ?? UIImage(named: "noImage1")!)
    .resizable()
    .aspectRatio(contentMode: .fit)
    .padding()
David Buck
  • 3,752
  • 35
  • 31
  • 35
Dc7
  • 1,405
  • 1
  • 10
  • 16
  • This is quite inefficient as `returnQRData` needlessly encodes the image as a PNG data stream only for it to be immediately decoded again. Using a CIContext to render to a CGImage will achieve the same thing without encoding to PNG – p10ben Nov 19 '21 at 05:30