|
|
import Foundation
|
|
|
import UIKit
|
|
|
import CoreImage.CIFilterBuiltins
|
|
|
|
|
|
// MARK: - 条形码生成器
|
|
|
class BarcodeGenerator {
|
|
|
|
|
|
// MARK: - 单例
|
|
|
static let shared = BarcodeGenerator()
|
|
|
|
|
|
private init() {}
|
|
|
|
|
|
// MARK: - 生成条形码图片
|
|
|
/// 根据内容和类型生成条形码图片
|
|
|
/// - Parameters:
|
|
|
/// - content: 条形码内容
|
|
|
/// - type: 条形码类型
|
|
|
/// - size: 图片尺寸,默认 CGSize(width: 300, height: 120)
|
|
|
/// - Returns: 生成的条形码图片
|
|
|
func generateBarcode(from content: String, type: String, size: CGSize = CGSize(width: 300, height: 120), showText: Bool = true) -> UIImage? {
|
|
|
guard !content.isEmpty else { return nil }
|
|
|
|
|
|
// 根据条形码类型选择合适的生成方式
|
|
|
switch type.lowercased() {
|
|
|
case let t where t.contains("ean13"):
|
|
|
return generateEAN13Barcode(from: content, size: size, showText: showText)
|
|
|
case let t where t.contains("ean8"):
|
|
|
return generateEAN8Barcode(from: content, size: size, showText: showText)
|
|
|
case let t where t.contains("upce"):
|
|
|
return generateUPCEBarcode(from: content, size: size, showText: showText)
|
|
|
case let t where t.contains("code39"):
|
|
|
return generateCode39Barcode(from: content, size: size, showText: showText)
|
|
|
case let t where t.contains("code128"):
|
|
|
return generateCode128Barcode(from: content, size: size, showText: showText)
|
|
|
case let t where t.contains("pdf417"):
|
|
|
return generatePDF417Barcode(from: content, size: size, showText: showText)
|
|
|
case let t where t.contains("itf14"):
|
|
|
return generateITF14Barcode(from: content, size: size, showText: showText)
|
|
|
default:
|
|
|
// 默认使用 Code 128
|
|
|
return generateCode128Barcode(from: content, size: size, showText: showText)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - EAN-13 条形码生成
|
|
|
private func generateEAN13Barcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
|
|
|
let context = CIContext()
|
|
|
let filter = CIFilter.code128BarcodeGenerator()
|
|
|
|
|
|
// EAN-13 需要13位数字
|
|
|
let paddedContent = content.padding(toLength: 13, withPad: "0", startingAt: 0)
|
|
|
filter.message = Data(paddedContent.utf8)
|
|
|
filter.quietSpace = 7.0
|
|
|
|
|
|
return processBarcodeFilter(filter, context: context, size: size, content: paddedContent, showText: showText)
|
|
|
}
|
|
|
|
|
|
// MARK: - EAN-8 条形码生成
|
|
|
private func generateEAN8Barcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
|
|
|
let context = CIContext()
|
|
|
let filter = CIFilter.code128BarcodeGenerator()
|
|
|
|
|
|
// EAN-8 需要8位数字
|
|
|
let paddedContent = content.padding(toLength: 8, withPad: "0", startingAt: 0)
|
|
|
filter.message = Data(paddedContent.utf8)
|
|
|
filter.quietSpace = 7.0
|
|
|
|
|
|
return processBarcodeFilter(filter, context: context, size: size, content: paddedContent, showText: showText)
|
|
|
}
|
|
|
|
|
|
// MARK: - UPC-E 条形码生成
|
|
|
private func generateUPCEBarcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
|
|
|
let context = CIContext()
|
|
|
let filter = CIFilter.code128BarcodeGenerator()
|
|
|
|
|
|
// UPC-E 需要8位数字
|
|
|
let paddedContent = content.padding(toLength: 8, withPad: "0", startingAt: 0)
|
|
|
filter.message = Data(paddedContent.utf8)
|
|
|
filter.quietSpace = 7.0
|
|
|
|
|
|
return processBarcodeFilter(filter, context: context, size: size, content: paddedContent, showText: showText)
|
|
|
}
|
|
|
|
|
|
// MARK: - Code 39 条形码生成
|
|
|
private func generateCode39Barcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
|
|
|
let context = CIContext()
|
|
|
let filter = CIFilter.code128BarcodeGenerator()
|
|
|
|
|
|
// Code 39 支持字母数字
|
|
|
filter.message = Data(content.utf8)
|
|
|
filter.quietSpace = 7.0
|
|
|
|
|
|
return processBarcodeFilter(filter, context: context, size: size, content: content, showText: showText)
|
|
|
}
|
|
|
|
|
|
// MARK: - Code 128 条形码生成
|
|
|
private func generateCode128Barcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
|
|
|
let context = CIContext()
|
|
|
let filter = CIFilter.code128BarcodeGenerator()
|
|
|
|
|
|
filter.message = Data(content.utf8)
|
|
|
filter.quietSpace = 7.0
|
|
|
|
|
|
return processBarcodeFilter(filter, context: context, size: size, content: content, showText: showText)
|
|
|
}
|
|
|
|
|
|
// MARK: - PDF417 条形码生成
|
|
|
private func generatePDF417Barcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
|
|
|
let context = CIContext()
|
|
|
let filter = CIFilter.pdf417BarcodeGenerator()
|
|
|
|
|
|
filter.message = Data(content.utf8)
|
|
|
filter.minWidth = 3
|
|
|
filter.maxWidth = 3
|
|
|
filter.minHeight = 3
|
|
|
filter.maxHeight = 3
|
|
|
|
|
|
return processBarcodeFilter(filter, context: context, size: size, content: content, showText: showText)
|
|
|
}
|
|
|
|
|
|
// MARK: - ITF-14 条形码生成
|
|
|
private func generateITF14Barcode(from content: String, size: CGSize, showText: Bool) -> UIImage? {
|
|
|
let context = CIContext()
|
|
|
let filter = CIFilter.code128BarcodeGenerator()
|
|
|
|
|
|
// ITF-14 需要14位数字
|
|
|
let paddedContent = content.padding(toLength: 14, withPad: "0", startingAt: 0)
|
|
|
filter.message = Data(paddedContent.utf8)
|
|
|
filter.quietSpace = 7.0
|
|
|
|
|
|
return processBarcodeFilter(filter, context: context, size: size, content: paddedContent, showText: showText)
|
|
|
}
|
|
|
|
|
|
// MARK: - 处理条形码滤镜
|
|
|
private func processBarcodeFilter(_ filter: CIFilter, context: CIContext, size: CGSize, content: String, showText: Bool) -> UIImage? {
|
|
|
guard let outputImage = filter.outputImage else { return nil }
|
|
|
|
|
|
// 计算缩放比例以适应目标尺寸
|
|
|
let scaleX = size.width / outputImage.extent.width
|
|
|
let scaleY = size.height / outputImage.extent.height
|
|
|
|
|
|
// 应用缩放变换
|
|
|
let transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
|
|
|
let scaledImage = outputImage.transformed(by: transform)
|
|
|
|
|
|
// 转换为 UIImage
|
|
|
guard let cgImage = context.createCGImage(scaledImage, from: scaledImage.extent) else { return nil }
|
|
|
|
|
|
let baseImage = UIImage(cgImage: cgImage)
|
|
|
|
|
|
// 如果需要显示文本,在条形码下方添加文本标签
|
|
|
if showText {
|
|
|
return addTextLabel(to: baseImage, content: content, size: size)
|
|
|
}
|
|
|
|
|
|
return baseImage
|
|
|
}
|
|
|
|
|
|
// MARK: - 添加文本标签
|
|
|
private func addTextLabel(to image: UIImage, content: String, size: CGSize) -> UIImage? {
|
|
|
let renderer = UIGraphicsImageRenderer(size: size)
|
|
|
|
|
|
return renderer.image { context in
|
|
|
// 绘制原始条形码图片
|
|
|
image.draw(in: CGRect(origin: .zero, size: size))
|
|
|
|
|
|
// 设置文本属性
|
|
|
let textAttributes: [NSAttributedString.Key: Any] = [
|
|
|
.font: UIFont.systemFont(ofSize: 12, weight: .medium),
|
|
|
.foregroundColor: UIColor.black
|
|
|
]
|
|
|
|
|
|
// 计算每个字符的位置,让每个数字对应其条形码位置
|
|
|
let charWidth = size.width / CGFloat(content.count)
|
|
|
let textY = size.height - 20 // 距离底部20像素
|
|
|
|
|
|
// 为每个字符绘制背景和文本
|
|
|
for (index, char) in content.enumerated() {
|
|
|
let charString = String(char)
|
|
|
let charSize = charString.size(withAttributes: textAttributes)
|
|
|
|
|
|
// 计算字符的X位置(居中对齐到对应的条形码区域)
|
|
|
let charX = CGFloat(index) * charWidth + (charWidth - charSize.width) / 2
|
|
|
|
|
|
// 绘制字符背景(白色背景,确保可读性)
|
|
|
let backgroundRect = CGRect(
|
|
|
x: charX - 2,
|
|
|
y: textY - 2,
|
|
|
width: charSize.width + 4,
|
|
|
height: charSize.height + 4
|
|
|
)
|
|
|
|
|
|
UIColor.white.setFill()
|
|
|
context.fill(backgroundRect)
|
|
|
|
|
|
// 绘制字符
|
|
|
charString.draw(at: CGPoint(x: charX, y: textY), withAttributes: textAttributes)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 获取条形码图标
|
|
|
/// 根据条形码类型获取对应的图标名称
|
|
|
/// - Parameter type: 条形码类型
|
|
|
/// - Returns: 图标名称
|
|
|
func getBarcodeIcon(for type: String) -> String {
|
|
|
let lowercasedType = type.lowercased()
|
|
|
|
|
|
if lowercasedType.contains("ean") || lowercasedType.contains("upc") {
|
|
|
return "barcode"
|
|
|
} else if lowercasedType.contains("code39") || lowercasedType.contains("code128") {
|
|
|
return "barcode.viewfinder"
|
|
|
} else if lowercasedType.contains("pdf417") {
|
|
|
return "qrcode.viewfinder"
|
|
|
} else if lowercasedType.contains("itf14") {
|
|
|
return "barcode"
|
|
|
} else {
|
|
|
return "barcode"
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 验证条形码内容
|
|
|
/// 验证条形码内容是否符合类型要求
|
|
|
/// - Parameters:
|
|
|
/// - content: 条形码内容
|
|
|
/// - type: 条形码类型
|
|
|
/// - Returns: 是否有效
|
|
|
func validateBarcodeContent(_ content: String, type: String) -> Bool {
|
|
|
guard !content.isEmpty else { return false }
|
|
|
|
|
|
let lowercasedType = type.lowercased()
|
|
|
|
|
|
switch lowercasedType {
|
|
|
case let t where t.contains("ean13"):
|
|
|
return content.count == 13 && content.allSatisfy { $0.isNumber }
|
|
|
case let t where t.contains("ean8"):
|
|
|
return content.count == 8 && content.allSatisfy { $0.isNumber }
|
|
|
case let t where t.contains("upce"):
|
|
|
return content.count == 8 && content.allSatisfy { $0.isNumber }
|
|
|
case let t where t.contains("itf14"):
|
|
|
return content.count == 14 && content.allSatisfy { $0.isNumber }
|
|
|
case let t where t.contains("code39"):
|
|
|
// Code 39 支持字母、数字和一些特殊字符
|
|
|
return content.allSatisfy { $0.isLetter || $0.isNumber || " -.$/+%".contains($0) }
|
|
|
case let t where t.contains("code128"):
|
|
|
// Code 128 支持 ASCII 字符
|
|
|
return content.allSatisfy { $0.asciiValue != nil }
|
|
|
case let t where t.contains("pdf417"):
|
|
|
// PDF417 支持二进制数据
|
|
|
return true
|
|
|
default:
|
|
|
return true
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - String 扩展
|
|
|
extension String {
|
|
|
/// 将字符串填充到指定长度
|
|
|
/// - Parameters:
|
|
|
/// - length: 目标长度
|
|
|
/// - pad: 填充字符
|
|
|
/// - startingAt: 开始位置
|
|
|
/// - Returns: 填充后的字符串
|
|
|
func padding(toLength length: Int, withPad pad: String, startingAt startIndex: Int) -> String {
|
|
|
if self.count >= length {
|
|
|
return String(self.prefix(length))
|
|
|
}
|
|
|
|
|
|
let paddingLength = length - self.count
|
|
|
let padding = String(repeating: pad, count: paddingLength)
|
|
|
return self + padding
|
|
|
}
|
|
|
} |