SwiftUI

SwiftUI 生成和缩放二维码

//
//  ContentView.swift
//  ORCode
//
//

import SwiftUI
import CoreImage.CIFilterBuiltins


struct ContentView: View {
    var body: some View {
        Image(uiImage: generateQRCode(from: "123")).interpolation(.none)
            .resizable()
            .scaledToFit()
            .frame(width: 100, height: 100)
            .padding()
    }
    
    func generateQRCode(from string: String) -> UIImage {
        let context = CIContext()
        let filter = CIFilter.qrCodeGenerator()
        let data = Data(string.utf8)
        filter.setValue(data, forKey: "inputMessage")

        if let outputImage = filter.outputImage {
            if let cgimg = context.createCGImage(outputImage, from: outputImage.extent) {
                return UIImage(cgImage: cgimg)
            }
        }

        return UIImage(systemName: "xmark.circle") ?? UIImage()
    }
}

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

生成和缩放二维码

Core Image 可以让我们基于任何字符串输入生成一个二维码,而且过程极快。不过,这里有一个问题:图像的尺寸很小,只包含承载数据必要的像素。要让二维码更好用,需要借助 SwiftUI 的图像插值。因此,在这一步我们要让用户在表单里输入他们的名字和邮件地址,然后用这两条信息生成一个能标识他们的二维码,并且放大这个二维码。

二维码是一块由黑白像素构成的方块,可以通过手机和其他设备来扫描。Core Image 对此提供了专门的滤镜。如果你之前学习过如何使用 Core Image 滤镜,你会发现下面的过程很相似。

首先,我们需要用一个新的 import 来导入 Core Image 内置的滤镜:

import CoreImage.CIFilterBuiltins

其次,我们要用两个属性来分别存储一个激活的 Core Image 上下文和一个 Core Image 的二维码生成器滤镜的实例。

let context = CIContext()<br>let filter = CIFilter.qrCodeGenerator()

接下来是有趣的部分:制作二维码。如果你还记得,Core Image 滤镜要求我们使用 setValue(_:forKey:) 来设置输入数据,然后把输出的 CIImage 转成 CGImage,再把 CGImage 转成 UIImage。这里的步骤相似,除了以下三点:

  • 我们的输入是字符串,但滤镜的输入是 Data,所以我们需要做转换。
  • 如果转换因为某种原因失败,我们会返回 SF Symbols 的 “xmark.circle” 图像。
  • 如果该图像不能读取 —— 理论上这是有可能的,因为 SF Symbols 是基于字符串的 —— 那么我们将返回一个空的 UIImage

把下面这个方法添加到 结构体:

func generateQRCode(from string: String) -> UIImage {
    let data = Data(string.utf8)
    filter.setValue(data, forKey: "inputMessage")

    if let outputImage = filter.outputImage {
        if let cgimg = context.createCGImage(outputImage, from: outputImage.extent) {
            return UIImage(cgImage: cgimg)
        }
    }

    return UIImage(systemName: "xmark.circle") ?? UIImage()
}

在 SwiftUI 中,通过函数分离功能的做法很奏效,因为这意味着我们放进 body 属性的代码将会尽可能地简单。实际上,我们可以直接使用 generateQRCode(from:) 生成的 Image,然后把它放大到一个合理的尺寸 .

把这个新 Image 视图直接添加到 视图上面

Image(uiImage: generateQRCode(from: "\(name)\n\(emailAddress)"))
    .resizable()
    .scaledToFit()
    .frame(width: 200, height: 200)

不过,近距离观察一下二维码 —— 你注意到它是模糊的吗?这是因为 Core Image 生成的是小图像,而 SwiftUI 在放大时尝试平滑像素。

像二维码这样的线条艺术,最好是禁用图像插值,把下面这个 modifier 添加到图像:

.interpolation(.none)

这样二维码就会以比较锐利的方式渲染,因为 SwiftUI 只会复制像素而不会混合像素。对于相机来说,它不关心二维码长啥样,但这样对用户来说比较好看。