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 } }