It’s now or never

IT系の技術ブログです。気になったこと、勉強したことを備忘録的にまとめて行きます。

【SwiftUI】SwiftUIでQRコードを表示する

概要

SwiftUIでQRコードを表示する必要があり、調べた内容になります。
UIKitと全く同じだと若干ハマりどころもあるため、備忘録として残しておきます。

環境

  • Swift: 5.2.2
  • Xcode: 11.4.1

QRCodeの画像を作成

import CoreImage
import UIKit

class QRCode {
    static func makeQRImage(_ input: String) -> UIImage {
        let inputData = input.data(using: .utf8)!
        // NOTE:誤り訂正レベルはとりあえず「Q」を指定
        let qrFilter = CIFilter(name: "CIQRCodeGenerator",
                          parameters: ["inputMessage": inputData,
                                       "inputCorrectionLevel": "Q"])
        let ciImage = qrFilter!.outputImage!
        // NOTE: 元のCIImageは小さいので任意のサイズに拡大
        let sizeTransform = CGAffineTransform(scaleX: 10, y: 10)
        let scaledCiImage = ciImage.transformed(by: sizeTransform)
        // NOTE: CIImageをそのまま変換するとImageで表示されないため一度CGImageに変換してからUIImageに変換する
        let context = CIContext()
        let qrCgImage = context.createCGImage(scaledCiImage, from: scaledCiImage.extent)!
        return UIImage(cgImage: qrCgImage)
    }
}

まず、QRコードを作成します。(簡単のため、エラー処理などは除外しています)

SwiftでQRコードを作成するには、CoreImage のCIFilerを使います。

let qr = CIFilter(name: "CIQRCodeGenerator",
                  parameters: ["inputMessage": inputData,
                               "inputCorrectionLevel": "Q"])

CiFilterに CIQRCodeGenerator という「name」を渡すことでQRコードのCIImageを作成することができます。
「parameters」には、inputMessage にQRCodeに変換したい文字列を Data 型のオブジェクトとしてわたします。
inputCorrectionLevelは、「誤り訂正レベル」というQRコードの仕様で「L」、「M」、「Q」、「H」のいずれかを指定します。

let ciImage = qrFilter!.outputImage!
// NOTE: 元のCIImageは小さいので任意のサイズに拡大
let sizeTransform = CGAffineTransform(scaleX: 10, y: 10)
let scaledCiImage = ciImage.transformed(by: sizeTransform)

作成したCIFilterからCIImageを取り出し、拡大処理をしています。
(そのままのCIImageは、かなりサイズが小さいので10倍位で300pt前後になると思います)

let context = CIContext()
let qrCgImage = context.createCGImage(scaledCiImage, from: scaledCiImage.extent)!
return UIImage(cgImage: qrCgImage)

最後に作成したCIImage を一旦 CGImage に変換して UIImageに変換します。

ここが、UIKitの場合と異なる箇所です。
UIKitを使う場合、画像の表示には UIImageView を使うのが一般的ですが、UIImageViewはCIImageから変換したUIImageを渡すだけで描画することができます。

しかし、SwiftUIの Image コンポーネントは、CIImageから変換したUIImageだと正しく描画することができません。(エラーにならないが描画されない)

(詳細な理由は理解できていないのですが、 SwiftUIのImageのイニシャライザに、UIImageとCIImageはあるがCGImageがないので描画方式に違いがあるのかもしれません)

そのため、一旦CGImageに変換したものを再度UIImageに変換します。

(前述の通り、Image コンポーネントは、直接 CGImage を受け取るイニシャライザをもっているため、UIImageに変換せず、CGImageを返すだけでも問題ありません。)

View側の処理

struct ContentView: View {
    var body: some View {
        VStack {
            Image(uiImage: QRCode.makeQRImage("test"))
        }
    }
}

View側は Image コンポーネントに作成した UIImageを渡すことでQRコードを表示できます。

所感

微妙なハマりどころですが、UIKitからコード移植する場合などは注意が必要かもしれません。
この辺のノウハウがもう少し世の中に公開されてくるとSwifUIの普及も進んでくるような気がします。