利用 Swift 在 iOS 構建獨特漂亮的 QR code 視圖


本篇原文(標題:Generating beautiful QR code views in iOS using Swift)刊登於作者 Medium,由 Prafulla Singh 所著,並授權翻譯及轉載。

QR Code 是一種認證技術,廣泛應用於行動科技領域中。iOS 內建支援創建帶有 Payload 的漂亮 QR Code 圖像。在本篇教學中,我們將會學習這個技巧。

QR code 的基本組件:

首先,讓我們先構建結構,然後再實作基本和漂亮的 QR Code 視圖。

struct QRCodeDataSet {
    let logo: UIImage?
    let url: String
    let backgroundColor: CIColor
    let color: CIColor
    let size: CGSize
    
    init(logo: UIImage? = nil, url: String) {
        self.logo = logo
        self.url = url
        self.backgroundColor = CIColor(red: 1,green: 1,blue: 1)
        self.color = CIColor(red: 1,green: 0.46,blue: 0.46)
        self.size = CGSize(width: 300, height: 300)
    }

    init(logo: UIImage? = nil, url: String, backgroundColor: CIColor, color: CIColor, size: CGSize) {
        self.logo = logo
        self.url = url
        self.backgroundColor = backgroundColor
        self.color = color
        self.size = size
    }
    }

這就是基本的結構,使用者要按需要傳遞 payload 和其他可選物件。

建立一個基本的 QR Code

使用 ‘CIQRCodeGenerator’ 過濾器建立一個 CIFilter 物件,然後利用 inputMessage 和修正鍵 inputCorrectionLevel 設置 Payload。 (在這個範例中,我們使用了 inputCorrectionLevel H。)

 private func createCIImage() -> CIImage? {
        guard let filter = CIFilter(name: "CIQRCodeGenerator") else {
            return nil
        }
        filter.setDefaults()
        filter.setValue(url.data(using: String.Encoding.ascii), forKey: "inputMessage")
        filter.setValue("H", forKey: "inputCorrectionLevel")
        //https://www.qrcode.com/en/about/error_correction.html
        return filter.outputImage
 }

然後會得到回傳的 CIImage:

basic-qr-code

為 QR Code 添加顏色

假色 (False Color) 的過濾器是 ‘CIFalseColor’。讓我們從前面函式提取圖像,並設置為輸入圖像 (Input Image),如此設置顏色:

private func updateColor(image: CIImage) -> CIImage? {
        guard let colorFilter = CIFilter(name: "CIFalseColor") else { return nil }
        
        colorFilter.setValue(image, forKey: kCIInputImageKey)
        colorFilter.setValue(color, forKey: "inputColor0")
        colorFilter.setValue(backgroundColor, forKey: "inputColor1")
        return colorFilter.outputImage
    }

然後會得到回傳的 CIImage:

code-with-color

為 QR Code 添加 Logo

讓我們創建新的過濾器 ‘CISourceOverCompositing’,然後將 Logo 圖像轉換為最終圖像的中心,將 Logo 設置為主圖像,並把前面函式中提取的圖像(也就是 QR 圖像)設置為背景。

private func addLogo(image: CIImage, logo: UIImage) -> CIImage? {
        
        guard let combinedFilter = CIFilter(name: "CISourceOverCompositing") else { return nil }
        guard let logo = logo.cgImage else {
            return image
        }
        
        let ciLogo = CIImage(cgImage: logo)

        
        let centerTransform = CGAffineTransform(translationX: image.extent.midX - (ciLogo.extent.size.width / 2), y: image.extent.midY - (ciLogo.extent.size.height / 2))
        
        combinedFilter.setValue(ciLogo.transformed(by: centerTransform), forKey: "inputImage")
        combinedFilter.setValue(image, forKey: "inputBackgroundImage")
        return combinedFilter.outputImage
    }

然後會得到回傳的 CIImage:

code-with-logo

完整程式碼

import SwiftUI
import Foundation
import UIKit

struct ContentView: View {
    
    
    var body: some View {
        ZStack {
            QRView().frame(width: 300, height: 300, alignment: .center)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct QRView: UIViewRepresentable {
    func updateUIView(_ uiView: UIImageView, context: Context) {
        
    }
    
   

    func makeUIView(context: Context) -> UIImageView {
        return UIImageView.init(image: QRCodeDataSet(logo: UIImage.init(named: "red-hat")!, url: "https://google.com").getQRImage())
    }

}


struct QRCodeDataSet {
    let logo: UIImage?
    let url: String
    let backgroundColor: CIColor
    let color: CIColor
    let size: CGSize
    
    init(logo: UIImage? = nil, url: String) {
        self.logo = logo
        self.url = url
        self.backgroundColor = CIColor(red: 1,green: 1,blue: 1)
        self.color = CIColor(red: 1,green: 0.46,blue: 0.46)
        self.size = CGSize(width: 300, height: 300)
    }
    //rgb(255,  , 118)
    init(logo: UIImage? = nil, url: String, backgroundColor: CIColor, color: CIColor, size: CGSize) {
        self.logo = logo
        self.url = url
        self.backgroundColor = backgroundColor
        self.color = color
        self.size = size
    }
    
    func getQRImage() -> UIImage? {
        
        guard var image  = createCIImage() else { return nil}
        
        
        ///scale to width:height
        let scaleW = self.size.width/image.extent.size.width
        let scaleH = self.size.height/image.extent.size.height
        let transform = CGAffineTransform(scaleX: scaleW, y: scaleH)
        image = image.transformed(by: transform)
        
        /// add logo
        if let logo = logo, let newImage =  addLogo(image: image, logo: logo) {
           image = newImage
        }
        
        
        /// update color
        if let colorImgae = updateColor(image: image) {
            image = colorImgae
        }
        
        return UIImage(ciImage: image)
        
    }
    
    private func updateColor(image: CIImage) -> CIImage? {
        guard let colorFilter = CIFilter(name: "CIFalseColor") else { return nil }
        
        colorFilter.setValue(image, forKey: kCIInputImageKey)
        colorFilter.setValue(color, forKey: "inputColor0")
        colorFilter.setValue(backgroundColor, forKey: "inputColor1")
        return colorFilter.outputImage
    }
    
    private func addLogo(image: CIImage, logo: UIImage) -> CIImage? {
        
        guard let combinedFilter = CIFilter(name: "CISourceOverCompositing") else { return nil }
        guard let logo = logo.cgImage else {
            return image
        }
        
        let ciLogo = CIImage(cgImage: logo)

        
        let centerTransform = CGAffineTransform(translationX: image.extent.midX - (ciLogo.extent.size.width / 2), y: image.extent.midY - (ciLogo.extent.size.height / 2))
        
        combinedFilter.setValue(ciLogo.transformed(by: centerTransform), forKey: "inputImage")
        combinedFilter.setValue(image, forKey: "inputBackgroundImage")
        return combinedFilter.outputImage
    }
    
    private func createCIImage() -> CIImage? {
        guard let filter = CIFilter(name: "CIQRCodeGenerator") else {
            return nil
        }
        filter.setDefaults()
        filter.setValue(url.data(using: String.Encoding.ascii), forKey: "inputMessage")
        filter.setValue("H", forKey: "inputCorrectionLevel")
        //https://www.qrcode.com/en/about/error_correction.html
        return filter.outputImage
    }
}

請記得更改圖像名稱,來讓程式碼可以正常運作。

參考資料

本篇原文(標題:Generating beautiful QR code views in iOS using Swift)刊登於作者 Medium,由 Prafulla Singh 所著,並授權翻譯及轉載。

作者簡介:Prafulla Singh,Block.one 的 iOS 開發者

譯者簡介:Kelly Chan-AppCoda 編輯小姐。


此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。

blog comments powered by Disqus
Shares
Share This