|
|
@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
import SwiftUI
|
|
|
|
|
|
|
|
import CoreImage
|
|
|
|
|
|
|
|
import QRCode
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - 二维码生成工具类
|
|
|
|
|
|
|
|
class QRCodeGenerator {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - 生成基本二维码(使用Core Image)
|
|
|
|
|
|
|
|
static func generateBasicQRCode(content: String) -> UIImage {
|
|
|
|
|
|
|
|
let data = content.data(using: .utf8)
|
|
|
|
|
|
|
|
let qrFilter = CIFilter.qrCodeGenerator()
|
|
|
|
|
|
|
|
qrFilter.setValue(data, forKey: "inputMessage")
|
|
|
|
|
|
|
|
qrFilter.setValue("H", forKey: "inputCorrectionLevel")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
guard let outputImage = qrFilter.outputImage else {
|
|
|
|
|
|
|
|
return UIImage(systemName: "qrcode") ?? UIImage()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let context = CIContext()
|
|
|
|
|
|
|
|
guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else {
|
|
|
|
|
|
|
|
return UIImage(systemName: "qrcode") ?? UIImage()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return UIImage(cgImage: cgImage)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - 生成高质量二维码(使用QRCode库)
|
|
|
|
|
|
|
|
static func generateHighQualityQRCode(
|
|
|
|
|
|
|
|
content: String,
|
|
|
|
|
|
|
|
styleData: QRCodeStyleData? = nil
|
|
|
|
|
|
|
|
) -> UIImage {
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
let document = try createQRCodeDocument(content: content, styleData: styleData)
|
|
|
|
|
|
|
|
let imageData = try document.pngData(dimension: 600)
|
|
|
|
|
|
|
|
return UIImage(data: imageData) ?? generateBasicQRCode(content: content)
|
|
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
|
|
print("生成高质量二维码失败:\(error.localizedDescription)")
|
|
|
|
|
|
|
|
return generateBasicQRCode(content: content)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - 创建QRCode文档
|
|
|
|
|
|
|
|
private static func createQRCodeDocument(
|
|
|
|
|
|
|
|
content: String,
|
|
|
|
|
|
|
|
styleData: QRCodeStyleData?
|
|
|
|
|
|
|
|
) throws -> QRCode.Document {
|
|
|
|
|
|
|
|
let document = try QRCode.Document(engine: QRCodeEngineExternal())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设置内容
|
|
|
|
|
|
|
|
document.utf8String = content
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有样式数据,使用默认样式
|
|
|
|
|
|
|
|
guard let styleData = styleData else {
|
|
|
|
|
|
|
|
// 使用默认样式
|
|
|
|
|
|
|
|
document.design.backgroundColor(CGColor(red: 1, green: 1, blue: 1, alpha: 1)) // 白色背景
|
|
|
|
|
|
|
|
document.design.style.eye = QRCode.FillStyle.Solid(CGColor(red: 0, green: 0, blue: 0, alpha: 1)) // 黑色眼睛
|
|
|
|
|
|
|
|
document.design.style.eyeBackground = CGColor(red: 1, green: 1, blue: 1, alpha: 1) // 白色眼睛背景
|
|
|
|
|
|
|
|
document.design.shape.onPixels = QRCode.PixelShape.Square() // 方形点
|
|
|
|
|
|
|
|
document.design.style.onPixels = QRCode.FillStyle.Solid(CGColor(red: 0, green: 0, blue: 0, alpha: 1)) // 黑色点
|
|
|
|
|
|
|
|
document.design.style.onPixelsBackground = CGColor(red: 1, green: 1, blue: 1, alpha: 1) // 白色点背景
|
|
|
|
|
|
|
|
document.design.shape.offPixels = QRCode.PixelShape.Square() // 方形空白
|
|
|
|
|
|
|
|
document.design.style.offPixels = QRCode.FillStyle.Solid(CGColor(red: 1, green: 1, blue: 1, alpha: 1)) // 白色空白
|
|
|
|
|
|
|
|
document.design.style.offPixelsBackground = CGColor(red: 1, green: 1, blue: 1, alpha: 1) // 白色空白背景
|
|
|
|
|
|
|
|
document.design.shape.eye = QRCode.EyeShape.Square() // 方形眼睛
|
|
|
|
|
|
|
|
return document
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 应用样式数据
|
|
|
|
|
|
|
|
applyStyleData(styleData, to: document)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return document
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - 应用样式数据
|
|
|
|
|
|
|
|
private static func applyStyleData(_ styleData: QRCodeStyleData, to document: QRCode.Document) {
|
|
|
|
|
|
|
|
// 设置背景色
|
|
|
|
|
|
|
|
if let backgroundColor = getColor(from: styleData.backgroundColor) {
|
|
|
|
|
|
|
|
document.design.backgroundColor(backgroundColor)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设置前景色
|
|
|
|
|
|
|
|
let foregroundColor = getColor(from: styleData.foregroundColor) ?? CGColor(red: 0, green: 0, blue: 0, alpha: 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设置眼睛样式
|
|
|
|
|
|
|
|
document.design.style.eye = QRCode.FillStyle.Solid(foregroundColor)
|
|
|
|
|
|
|
|
document.design.style.eyeBackground = getColor(from: styleData.backgroundColor) ?? CGColor(red: 1, green: 1, blue: 1, alpha: 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设置点样式
|
|
|
|
|
|
|
|
let dotType = getDotType(from: styleData.dotType)
|
|
|
|
|
|
|
|
document.design.shape.onPixels = dotType
|
|
|
|
|
|
|
|
document.design.style.onPixels = QRCode.FillStyle.Solid(foregroundColor)
|
|
|
|
|
|
|
|
document.design.style.onPixelsBackground = getColor(from: styleData.backgroundColor) ?? CGColor(red: 1, green: 1, blue: 1, alpha: 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.design.shape.offPixels = dotType
|
|
|
|
|
|
|
|
document.design.style.offPixels = QRCode.FillStyle.Solid(getColor(from: styleData.backgroundColor) ?? CGColor(red: 1, green: 1, blue: 1, alpha: 1))
|
|
|
|
|
|
|
|
document.design.style.offPixelsBackground = getColor(from: styleData.backgroundColor) ?? CGColor(red: 1, green: 1, blue: 1, alpha: 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设置眼睛形状
|
|
|
|
|
|
|
|
let eyeType = getEyeType(from: styleData.eyeType)
|
|
|
|
|
|
|
|
document.design.shape.eye = eyeType
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设置Logo
|
|
|
|
|
|
|
|
if let logo = styleData.logo {
|
|
|
|
|
|
|
|
applyLogo(logo, hasCustomLogo: styleData.hasCustomLogo, customLogoFileName: styleData.customLogoFileName, to: document)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - 应用Logo
|
|
|
|
|
|
|
|
private static func applyLogo(
|
|
|
|
|
|
|
|
_ logoIdentifier: String,
|
|
|
|
|
|
|
|
hasCustomLogo: Bool,
|
|
|
|
|
|
|
|
customLogoFileName: String?,
|
|
|
|
|
|
|
|
to document: QRCode.Document
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
if hasCustomLogo, let fileName = customLogoFileName {
|
|
|
|
|
|
|
|
// 加载自定义Logo
|
|
|
|
|
|
|
|
if let customLogoImage = loadCustomLogoImage(fileName: fileName),
|
|
|
|
|
|
|
|
let cgImage = customLogoImage.cgImage {
|
|
|
|
|
|
|
|
document.logoTemplate = QRCode.LogoTemplate.CircleCenter(image: cgImage, inset: 0)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// 加载预设Logo
|
|
|
|
|
|
|
|
if let qrCodeLogo = QRCodeLogo(rawValue: logoIdentifier),
|
|
|
|
|
|
|
|
let logoImage = qrCodeLogo.image,
|
|
|
|
|
|
|
|
let cgImage = logoImage.cgImage {
|
|
|
|
|
|
|
|
document.logoTemplate = QRCode.LogoTemplate.CircleCenter(image: cgImage)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - 加载自定义Logo图片
|
|
|
|
|
|
|
|
private static func loadCustomLogoImage(fileName: String) -> UIImage? {
|
|
|
|
|
|
|
|
guard let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let customLogosPath = documentsPath.appendingPathComponent("CustomLogos")
|
|
|
|
|
|
|
|
let imagePath = customLogosPath.appendingPathComponent(fileName)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return UIImage(contentsOfFile: imagePath.path)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - 颜色转换
|
|
|
|
|
|
|
|
private static func getColor(from colorString: String) -> CGColor? {
|
|
|
|
|
|
|
|
switch colorString.lowercased() {
|
|
|
|
|
|
|
|
case "black": return CGColor(red: 0, green: 0, blue: 0, alpha: 1)
|
|
|
|
|
|
|
|
case "white": return CGColor(red: 1, green: 1, blue: 1, alpha: 1)
|
|
|
|
|
|
|
|
case "red": return CGColor(red: 1, green: 0, blue: 0, alpha: 1)
|
|
|
|
|
|
|
|
case "blue": return CGColor(red: 0, green: 0, blue: 1, alpha: 1)
|
|
|
|
|
|
|
|
case "green": return CGColor(red: 0, green: 1, blue: 0, alpha: 1)
|
|
|
|
|
|
|
|
case "yellow": return CGColor(red: 1, green: 1, blue: 0, alpha: 1)
|
|
|
|
|
|
|
|
case "purple": return CGColor(red: 0.5, green: 0, blue: 0.5, alpha: 1)
|
|
|
|
|
|
|
|
case "orange": return CGColor(red: 1, green: 0.5, blue: 0, alpha: 1)
|
|
|
|
|
|
|
|
case "pink": return CGColor(red: 1, green: 0.75, blue: 0.8, alpha: 1)
|
|
|
|
|
|
|
|
case "cyan": return CGColor(red: 0, green: 1, blue: 1, alpha: 1)
|
|
|
|
|
|
|
|
case "magenta": return CGColor(red: 1, green: 0, blue: 1, alpha: 1)
|
|
|
|
|
|
|
|
case "brown": return CGColor(red: 0.6, green: 0.4, blue: 0.2, alpha: 1)
|
|
|
|
|
|
|
|
case "gray": return CGColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1)
|
|
|
|
|
|
|
|
case "navy": return CGColor(red: 0, green: 0, blue: 0.5, alpha: 1)
|
|
|
|
|
|
|
|
case "teal": return CGColor(red: 0, green: 0.5, blue: 0.5, alpha: 1)
|
|
|
|
|
|
|
|
case "indigo": return CGColor(red: 0.3, green: 0, blue: 0.7, alpha: 1)
|
|
|
|
|
|
|
|
case "lime": return CGColor(red: 0.5, green: 1, blue: 0, alpha: 1)
|
|
|
|
|
|
|
|
case "maroon": return CGColor(red: 0.5, green: 0, blue: 0, alpha: 1)
|
|
|
|
|
|
|
|
case "olive": return CGColor(red: 0.5, green: 0.5, blue: 0, alpha: 1)
|
|
|
|
|
|
|
|
case "silver": return CGColor(red: 0.75, green: 0.75, blue: 0.75, alpha: 1)
|
|
|
|
|
|
|
|
default: return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - 点类型转换
|
|
|
|
|
|
|
|
private static func getDotType(from dotTypeString: String) -> QRCodePixelShapeGenerator {
|
|
|
|
|
|
|
|
// 根据字符串匹配到对应的QRCodeDotType
|
|
|
|
|
|
|
|
if let dotType = QRCodeDotType.allCases.first(where: { $0.rawValue == dotTypeString }) {
|
|
|
|
|
|
|
|
return dotType.pixelShape
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有找到匹配的类型,返回默认的方形
|
|
|
|
|
|
|
|
return QRCode.PixelShape.Square()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// MARK: - 眼睛类型转换
|
|
|
|
|
|
|
|
private static func getEyeType(from eyeTypeString: String) -> QRCodeEyeShapeGenerator {
|
|
|
|
|
|
|
|
// 根据字符串匹配到对应的QRCodeEyeType
|
|
|
|
|
|
|
|
if let eyeType = QRCodeEyeType.allCases.first(where: { $0.rawValue == eyeTypeString }) {
|
|
|
|
|
|
|
|
return eyeType.eyeShape
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果没有找到匹配的类型,返回默认的方形
|
|
|
|
|
|
|
|
return QRCode.EyeShape.Square()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|