[SwiftUI 100 天] 生成和缩放二维码

1,368 阅读4分钟

译自 www.hackingwithswift.com/books/ios-s…

更多内容,欢迎关注公众号 「Swift花园」

喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀

生成和缩放二维码

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

我们已经有一个简单的 MeView 占位,第一个任务是添加一组文本框和对应的字符串绑定。

把保存名称和邮件地址的状态添加到 MeView

@State private var name = "Anonymous"
@State private var emailAddress = "you@yoursite.com"

至于视图的 body,我们将使用两个大字号的文本框,然后用 spacer 把它们顶到屏幕上方。这里我们要对文本框使用一个小巧但有用的 modifier,它叫 textContentType(),它会告诉 iOS 我们要求用户输入的信息的类型。这可以帮助 iOS 基于用户行为提供自动完成数据的行为,从而提升应用使用体验。

把当前的 body 替换为下面的代码:

NavigationView {
    VStack {
        TextField("Name", text: $name)
            .textContentType(.name)
            .font(.title)
            .padding(.horizontal)

        TextField("Email address", text: $emailAddress)
            .textContentType(.emailAddress)
            .font(.title)
            .padding([.horizontal, .bottom])

        Spacer()
    }
    .navigationBarTitle("Your code")
}

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

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

import CoreImage.CIFilterBuiltins

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

let context = CIContext()
let filter = CIFilter.qrCodeGenerator()

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

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

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

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,然后把它放大到一个合理的尺寸 —— SwiftUI 会确保每一次 name 或者 emailAddress 发生改变时该方法都被调用。

对于传入 generateQRCode(from:) 的字符串,我们将使用用户输入的名称和邮件地址,以换行分隔。这是一个简单实用的格式,在从二维码转换回来时也很容易。

把这个新 Image 视图直接添加到 Spacer 后面:

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

运行代码 —— 你将看到默认的二维码,通过两个文本框的输入,你可以动态地改变二维码。

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

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

.interpolation(.none)

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


我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~

Swift花园微信公众号