二维码 2: 如何用 swift 设置二维码的样式

这里每天分享一个 iOS 的新知识,快来关注我吧

前言

昨天的文章讲了二维码的生成方法,感兴趣可以先去看一下。

二维码 1: 如何用 swift 生成二维码

一个二维码通常会存储字符串内容,所以可以给 String 创建一个扩展,以便捷使用:

swift 复制代码
extension String {
    func qrCodeImage(size: CGFloat) -> UIImage? {
        guard let filter = CIFilter(name: "CIQRCodeGenerator") else {
            return nil
        }
        
        let data = data(using: .utf8)
        
        filter.setValue(data, forKey: "inputMessage")
        guard let ciImage = filter.outputImage else {
            return nil
        }
        
        let scaleX = size / ciImage.extent.size.width
        let scaleY = size / ciImage.extent.size.height
        let transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
        
        return UIImage(ciImage: ciImage.transformed(by: transform))
    }
}

那么使用的时候就很简单了:

ini 复制代码
let url = "http://weixin.qq.com/r/QR0qLvXE6D2UKbFNb0jF"
// 创建一个 200 x 200 的二维码
let imageView = UIImageView(image: url.qrCodeImage(size: 200))
view.addSubview(imageView)

今天来讲讲如何自定义二维码的样式。

设置前景色和背景色

要设置前景色和背景色,主要用到 CIFalseColor 这个过滤器,简单原理是使用 CIFilter 创建一个名为 CIFalseColor 的过滤器,传入二维码图像和前景色 + 背景色,最终再从新的过滤器中生成新的图像:

swift 复制代码
extension String {
    
    /// 生成一个二维码
    /// - Parameters:
    ///   - size: 二维码的尺寸
    ///   - foregroundColor: 二维码的前景色
    ///   - backgroundColor: 二维码的背景色
    /// - Returns: 最终生成的二维码图片
    func qrCodeImage(size: CGFloat,
                     foregroundColor: UIColor,
                     backgroundColor: UIColor) -> UIImage? {
        guard let filter = CIFilter(name: "CIQRCodeGenerator") else {
            return nil
        }
        
        let data = data(using: .utf8)
        
        filter.setValue(data, forKey: "inputMessage")
        
        let colorFilter = CIFilter(name: "CIFalseColor", parameters: [
            "inputImage": filter.outputImage as Any,
            "inputColor0": CIColor(color: foregroundColor),
            "inputColor1": CIColor(color: backgroundColor)
        ])
        
        guard let ciImage = colorFilter?.outputImage else {
            return nil
        }
        
        let scaleX = size / ciImage.extent.size.width
        let scaleY = size / ciImage.extent.size.height
        let transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
        
        return UIImage(ciImage: ciImage.transformed(by: transform))
    }
}

然后我们就可以生成一个带有自定义前景色和背景色的二维码了:

less 复制代码
let url = "http://weixin.qq.com/r/QR0qLvXE6D2UKbFNb0jF"
// 创建一个 200 x 200 的二维码
let imageView = UIImageView(image: url.qrCodeImage(size: 200, foregroundColor: .red, backgroundColor: .blue))
view.addSubview(imageView)

我们经常见到二维码的中心会有个自定义 logo,那么如何实现这个功能呢?实际上还是需要在 CIImage 上下功夫。

这次用到的是 CISourceOverCompositing,这个过滤器可以将两个图层合并,为了使用方便,我们给 CIImage 添加一个 addImage 的方法:

swift 复制代码
extension CIImage {
    /// 向 CIImage 中心添加一个图片
    /// - Parameters:
    ///   - image: 要添加的图片
    ///   - size: 图片尺寸
    /// - Returns: 添加之后的图片
    func add(image: UIImage, size: CGFloat) -> CIImage? {
        guard let combinedFilter = CIFilter(name: "CISourceOverCompositing") else { return nil }
        guard let cgImage = image.cgImage else {
            return nil
        }
        
        let scaleX = size / image.size.width
        let scaleY = size / image.size.height
        let scaleTransform = CGAffineTransform(scaleX: scaleX, y: scaleY)
        let centerTransform = CGAffineTransform(translationX: extent.midX - (size / 2), y: extent.midY - (size / 2))
        
        combinedFilter.setValue(CIImage(cgImage: cgImage).transformed(by: scaleTransform).transformed(by: centerTransform), forKey: "inputImage")
        combinedFilter.setValue(self, forKey: "inputBackgroundImage")
        return combinedFilter.outputImage
    }
}

最终再给 qrCodeImage 加上 logologoSize 参数,贴一下最终代码:

swift 复制代码
extension String {
    
    /// 生成一个二维码
    /// - Parameters:
    ///   - size: 二维码的尺寸
    ///   - foregroundColor: 二维码的前景色
    ///   - backgroundColor: 二维码的背景色
    ///   - logo: 要在二维码中心添加的 logo
    ///   - logoSize: logo 的尺寸
    /// - Returns: 最终生成的二维码图片
    func qrCodeImage(size: CGFloat,
                     foregroundColor: UIColor,
                     backgroundColor: UIColor,
                     logo: UIImage,
                     logoSize: CGFloat) -> UIImage? {
        guard let filter = CIFilter(name: "CIQRCodeGenerator") else {
            return nil
        }
        
        let data = data(using: .utf8)
        
        filter.setValue(data, forKey: "inputMessage")
        
        let colorFilter = CIFilter(name: "CIFalseColor", parameters: [
            "inputImage": filter.outputImage as Any,
            "inputColor0": CIColor(color: foregroundColor),
            "inputColor1": CIColor(color: backgroundColor)
        ])
        
        guard let ciImage = colorFilter?.outputImage else {
            return nil
        }
        
        let scaleX = size / ciImage.extent.size.width
        let scaleY = size / ciImage.extent.size.height
        let transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
        let newCIImage = ciImage.transformed(by: transform)
        
        guard let newImage = newCIImage.add(image: logo, size: logoSize) else {
            return nil
        }
        
        return UIImage(ciImage: newImage)
    }
}

这时候就可以使用这个方法往二维码上添加 logo 了:

less 复制代码
let url = "http://weixin.qq.com/r/QR0qLvXE6D2UKbFNb0jF"
// 创建一个 200 x 200 的二维码
let qrImage = url.qrCodeImage(size: 200,
                              foregroundColor: .red,
                              backgroundColor: .blue,
                              logo: UIImage(named: "logo.png")!,
                              logoSize: 50)
let imageView = UIImageView(image: qrImage)
view.addSubview(imageView)

最后看下效果:

最后

向二维码添加 logo 之后会盖住一部分二维码,如果 logo 尺寸过大,可能会导致无法正常识别。

如果你在使用过程中遇到这个问题,可以参考下下边的解决方法,二维码生成的时候可以添加一个纠错级别,大概讲一讲:

CIQRCodeGenerator 过滤器支持以下两个参数:

  1. inputMessage: 一个 Data 类型的值,代表要编码的数据。这是必须的参数,因为没有它,二维码就没有任何意义。
  2. inputCorrectionLevel: 一个表示纠错级别的字符串。它可以是 "L", "M", "Q" 或 "H"。"L" 表示 7% 的代码字可以被修正。"M" 表示 15% 的代码字可以被修正。"Q" 表示 25% 的代码字可以被修正。"H" 表示 30% 的代码字可以被修正。这个参数是可选的,如果没有提供,那么将会使用默认级别 "M"。

例如:

less 复制代码
let qrFilter = CIFilter(name: "CIQRCodeGenerator")
qrFilter?.setValue(data, forKey: "inputMessage") // 必须的参数
qrFilter?.setValue("Q", forKey: "inputCorrectionLevel") // 可选的参数

在这个例子中,我们使用 "Q" 级别的纠错,这意味着即使二维码的 25% 被破坏,也仍然可以被成功读取。这在某些情况下可能会非常有用,例如当二维码可能会被部分遮挡或破坏,或者需要在二维码上添加 logo 时。

这里每天分享一个 iOS 的新知识,快来关注我吧

本文同步自微信公众号 "iOS新知",每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!

相关推荐
Magnetic_h15 小时前
【iOS】单例模式
笔记·学习·ui·ios·单例模式·objective-c
归辞...17 小时前
「iOS」——单例模式
ios·单例模式·cocoa
yanling202318 小时前
黑神话悟空mac可以玩吗
macos·ios·crossove·crossove24
归辞...20 小时前
「iOS」viewController的生命周期
ios·cocoa·xcode
crasowas1 天前
Flutter问题记录 - 适配Xcode 16和iOS 18
flutter·ios·xcode
2401_852403551 天前
Mac导入iPhone的照片怎么删除?快速方法讲解
macos·ios·iphone
SchneeDuan1 天前
iOS六大设计原则&&设计模式
ios·设计模式·cocoa·设计原则
JohnsonXin2 天前
【兼容性记录】video标签在 IOS 和 安卓中的问题
android·前端·css·ios·h5·兼容性
蒙娜丽宁2 天前
Go语言错误处理详解
ios·golang·go·xcode·go1.19
名字不要太长 像我这样就好2 天前
【iOS】push和pop、present和dismiss
学习·macos·ios·objective-c·cocoa