【SwiftUI】QRコードを生成して、中心に可愛いアイコンを付ける方法

2022.10.17

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

今回はSwiftUIでQRコードを生成して、ブランディングの為にQRコードの中心に可愛いアイコンを付ける方法を紹介したいと思います。

環境

  • Xcode 14
  • Swift 5.7

QRコードを生成する

QRコードを生成する構造体を作成しました。

QRCodeGenerator

import SwiftUI

struct QRCodeGenerator {

    func generate(with inputText: String) -> UIImage? {

        guard let qrFilter = CIFilter(name: "CIQRCodeGenerator")
        else { return nil }

        let inputData = inputText.data(using: .utf8)
        qrFilter.setValue(inputData, forKey: "inputMessage")
        // 誤り訂正レベルをHに指定
        qrFilter.setValue("H", forKey: "inputCorrectionLevel")

        guard let ciImage = qrFilter.outputImage
        else { return nil }

        // CIImageは小さい為、任意のサイズに拡大
        let sizeTransform = CGAffineTransform(scaleX: 10, y: 10)
        let scaledCiImage = ciImage.transformed(by: sizeTransform)

        // CIImageだとSwiftUIのImageでは表示されない為、CGImageに変換
        let context = CIContext()
        guard let cgImage = context.createCGImage(scaledCiImage,
                                                  from: scaledCiImage.extent)
        else { return nil }

        return UIImage(cgImage: cgImage)
    }
}

QRコード用のCIFilterからCIImageを作成する

guard let qrFilter = CIFilter(name: "CIQRCodeGenerator")
else { return nil }

let inputData = inputText.data(using: .utf8)
qrFilter.setValue(inputData, forKey: "inputMessage")
// 誤り訂正レベルをHに指定
qrFilter.setValue("H", forKey: "inputCorrectionLevel")

guard let ciImage = qrFilter.outputImage
else { return nil }

// CIImageは小さい為、任意のサイズに拡大
let sizeTransform = CGAffineTransform(scaleX: 10, y: 10)
let scaledCiImage = ciImage.transformed(by: sizeTransform)

CIFilter(name: "CIQRCodeGenerator")でQRコード用のCIFilterを生成することが出来ます。

引数として受け取っているinputTextをData型に変換して、qrFliter.setValueでKey inputMessagevalueにその値を指定します。

また、qrFliter.setValueでKey inputCorrectionLevelvalueに誤り補正レベルを指定します。

そして、qrFilter.outputImageからQRコードのCIImageを受け取っています。

そのままのCIImageではサイズが小さい為、任意のサイズにtransformedを使用して拡大しています。

誤り補正レベルとは、

QRコードは汚れていたり破損していたりしていてもある程度自ら復元することが可能です。誤り訂正レベルは4段階存在し、高レベルであれば訂正能力は上がりますが、コードのサイズは大きくなります。

引用: 【iOS】SwiftでQRコードを表示して目で読んでみた

CIImageからCGImageに変換する

// CIImageだとSwiftUIのImageでは表示されない為、CGImageに変換
let context = CIContext()
guard let cgImage = context.createCGImage(scaledCiImage,
                                          from: scaledCiImage.extent)
else { return nil }

return UIImage(cgImage: cgImage)

UIKitでUIImage(ciImage:)を表示する場合だと表示されるのですが、SwiftUIのImageCIImageから生成したUIImageを表示させた場合には画面にQRコードが表示されませんでした。

こちらはCIImageCGImageに変換して、UIImage(cgImage:)に渡すことで解決出来ました。

実際に動かしてみる

QRコードを生成するモデルは出来たので、実際にViewと組み合わせて使ってみます。

ContentView

import SwiftUI

struct ContentView: View {

    @State private var qrCodeImage: UIImage?
    private let qrCodeGenerator = QRCodeGenerator()

    var body: some View {
        VStack(spacing: 16) {

            if let qrCodeImage {
                Image(uiImage: qrCodeImage)
                    .resizable()
                    .frame(width: 200, height: 200)

            } else {
                ReloadButton {
                    qrCodeImage = qrCodeGenerator.generate(with: "https://careers.classmethod.jp/")
                }
            }

            Text("Hello, QRCode!")
        }
        .padding()
    }
}

QRコードの画像がnilではない場合は、QRコード画像が表示され、nilの場合はリロードボタンが表示されます。

ReloadButton

import SwiftUI

struct ReloadButton: View {

    let action: () -> Void

    var body: some View {

        VStack {
            Text("データを取得出来ませんでした")

            Button {
                action()
            } label: {
                Text("再取得")
            }
        }
    }
}

デモ

画面表示時には、まだ特にQR生成処理を実行していないので、QRコードは表示されません。リロードボタンを押すと、QRコードが生成されます。

generate-qr-code-without-icon

無事にQRコードの生成と表示は出来ました。次はせっかくなのでQRコードの中心にアイコンを設置してQRコードを可愛く仕上げましょう。

QRコードの中心に画像を設置する

UIGraphicsImageRendererを使用して、画像を重ねて一つのUIImageを作成します。

import UIKit

extension UIImage {

    // UIImageの中心に小さいUIImageを配置して合成する
    func composited(withSmallCenterImage centerImage: UIImage) -> UIImage {

        return UIGraphicsImageRenderer(size: self.size).image { context in

            let imageWidth = context.format.bounds.width
            let imageHeight = context.format.bounds.height
            let centerImageLength = imageWidth < imageHeight ? imageWidth / 5 : imageHeight / 5
            let centerImageRadius = centerImageLength * 0.2

            // 背面に設置する親画像を描画
            draw(in: CGRect(origin: CGPoint(x: 0, y: 0),
                            size: context.format.bounds.size))

            // 中心に設置する画像のrectを設定
            let centerImageRect = CGRect(x: (imageWidth - centerImageLength) / 2,
                                         y: (imageHeight - centerImageLength) / 2,
                                         width: centerImageLength,
                                         height: centerImageLength)

            // 画像に角丸をつける為のパスを作成
            let roundedRectPath = UIBezierPath(roundedRect: centerImageRect,
                                               cornerRadius: centerImageRadius)
            // クリッピングパスを追加
            roundedRectPath.addClip()

            // 中心に設置する画像を描画
            centerImage.draw(in: centerImageRect)
        }
    }
}

これで中心に可愛いアイコンがついたQRコードを作成できました!

今回は、正方形で角丸のアイコンのような画像が中心に配置されるようにしていますが、好みによって値は変更してみてください。

仕上げ

あとは、QRCodeGeneratorgenerate(with:)返り値の箇所に上記で作成したcomposited(withSmallCenterImage:)を使用するだけです。

return UIImage(cgImage: cgImage).composited(withSmallCenterImage: UIImage(named: "icon_cute")!)

完成したもの

generate-qr-code-with-icon

おわりに

これで一味違った可愛らしいQRコードを作成することが出来ました!ちょっとした手間でQRコードが可愛くなるのでQRコード作成の際は是非試してみてください。

完成したものはGitHubに載せているので気になる方は見てみてください。

またクラスメソッドでは一緒に働く仲間を募集中です。このQRコードを読み取ってその先に進んでみてください。

お待ちしています!

おわり

参考