Enhance HistoryItemRow in HistoryView to include navigation links for barcode details, improving user interaction and data type handling.

main
v504 2 months ago
parent f23fb833f9
commit 6e08511157

@ -0,0 +1,274 @@
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
}
}

@ -0,0 +1,352 @@
import SwiftUI
import CoreData
struct BarcodeDetailView: View {
let historyItem: HistoryItem
@StateObject private var coreDataManager = CoreDataManager.shared
@State private var barcodeImage: UIImage?
@State private var showingShareSheet = false
@State private var showingAlert = false
@State private var alertMessage = ""
var body: some View {
ScrollView {
VStack(spacing: 20) {
//
barcodeImageView
//
barcodeTypeSection
//
barcodeContentSection
//
originalContentSection
//
actionButtonsSection
}
.padding()
}
.navigationTitle("条形码详情")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
showingShareSheet = true
}) {
Image(systemName: "square.and.arrow.up")
}
}
}
.onAppear {
generateBarcodeImage()
}
.sheet(isPresented: $showingShareSheet) {
ShareSheet(activityItems: [historyItem.content ?? ""])
}
.alert("提示", isPresented: $showingAlert) {
Button("确定") { }
} message: {
Text(alertMessage)
}
}
// MARK: -
private var barcodeImageView: some View {
VStack(spacing: 16) {
if let barcodeImage = barcodeImage {
Image(uiImage: barcodeImage)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 120)
.cornerRadius(12)
.shadow(radius: 8)
} else {
RoundedRectangle(cornerRadius: 12)
.fill(Color.gray.opacity(0.3))
.frame(height: 120)
.overlay(
ProgressView()
.scaleEffect(1.5)
)
}
Text("扫描此条形码")
.font(.caption)
.foregroundColor(.secondary)
}
}
// MARK: -
private var barcodeTypeSection: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Image(systemName: "barcode")
.font(.title2)
.foregroundColor(.green)
Text("条形码类型")
.font(.headline)
Spacer()
}
if let barcodeTypeString = historyItem.barcodeType {
HStack {
Image(systemName: getBarcodeIcon(for: barcodeTypeString))
.font(.title3)
.foregroundColor(.green)
Text(barcodeTypeString)
.font(.title3)
.fontWeight(.medium)
Spacer()
}
.padding()
.background(Color.green.opacity(0.1))
.cornerRadius(8)
}
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(radius: 2)
}
// MARK: -
private var barcodeContentSection: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Image(systemName: "info.circle")
.font(.title2)
.foregroundColor(.blue)
Text("条形码内容")
.font(.headline)
Spacer()
}
if let content = historyItem.content {
VStack(alignment: .leading, spacing: 8) {
HStack {
Image(systemName: "number")
.font(.title3)
.foregroundColor(.blue)
Text("内容长度: \(content.count) 字符")
.font(.title3)
.fontWeight(.medium)
Spacer()
}
Text("数据内容")
.font(.subheadline)
.foregroundColor(.secondary)
.padding(.top, 4)
}
.padding()
.background(Color.blue.opacity(0.1))
.cornerRadius(8)
}
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(radius: 2)
}
// MARK: -
private var originalContentSection: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Image(systemName: "doc.text")
.font(.title2)
.foregroundColor(.purple)
Text("原始内容")
.font(.headline)
Spacer()
}
if let content = historyItem.content {
ScrollView {
Text(content)
.font(.system(.body, design: .monospaced))
.foregroundColor(.secondary)
.multilineTextAlignment(.leading)
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
}
.frame(maxHeight: 200)
.background(Color.purple.opacity(0.1))
.cornerRadius(8)
}
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(radius: 2)
}
// MARK: -
private var actionButtonsSection: some View {
VStack(spacing: 12) {
//
Button(action: toggleFavorite) {
HStack {
Image(systemName: historyItem.isFavorite ? "heart.fill" : "heart")
.foregroundColor(historyItem.isFavorite ? .red : .gray)
Text(historyItem.isFavorite ? "取消收藏" : "收藏")
.fontWeight(.medium)
}
.frame(maxWidth: .infinity)
.padding()
.background(historyItem.isFavorite ? Color.red.opacity(0.1) : Color.gray.opacity(0.1))
.foregroundColor(historyItem.isFavorite ? .red : .gray)
.cornerRadius(10)
}
//
Button(action: copyContent) {
HStack {
Image(systemName: "doc.on.doc")
.foregroundColor(.blue)
Text("复制内容")
.fontWeight(.medium)
}
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue.opacity(0.1))
.foregroundColor(.blue)
.cornerRadius(10)
}
//
if barcodeImage != nil {
Button(action: {
showingShareSheet = true
}) {
HStack {
Image(systemName: "photo")
.foregroundColor(.green)
Text("分享条形码图片")
.fontWeight(.medium)
}
.frame(maxWidth: .infinity)
.padding()
.background(Color.green.opacity(0.1))
.foregroundColor(.green)
.cornerRadius(10)
}
}
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(radius: 2)
}
// MARK: -
private func generateBarcodeImage() {
guard let content = historyItem.content else { return }
// 使
let barcodeType = historyItem.barcodeType ?? ""
let size = CGSize(width: 300, height: 120)
if let image = BarcodeGenerator.shared.generateBarcode(from: content, type: barcodeType, size: size) {
self.barcodeImage = image
}
}
// MARK: -
private func getBarcodeIcon(for type: String) -> String {
return BarcodeGenerator.shared.getBarcodeIcon(for: type)
}
// MARK: -
private func toggleFavorite() {
historyItem.isFavorite.toggle()
coreDataManager.save()
let message = historyItem.isFavorite ? "已添加到收藏" : "已取消收藏"
alertMessage = message
showingAlert = true
}
// MARK: -
private func copyContent() {
if let content = historyItem.content {
UIPasteboard.general.string = content
alertMessage = "内容已复制到剪贴板"
showingAlert = true
}
}
}
// MARK: -
struct BarcodeShareSheet: UIViewControllerRepresentable {
let activityItems: [Any]
func makeUIViewController(context: Context) -> UIActivityViewController {
let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
return controller
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {}
}
#Preview("EAN-13") {
let ctx = PreviewData.context
let item = PreviewData.ean13Sample(in: ctx)
return NavigationView { BarcodeDetailView(historyItem: item) }
}
#Preview("Code 128") {
let ctx = PreviewData.context
let item = PreviewData.code128Sample(in: ctx)
return NavigationView { BarcodeDetailView(historyItem: item) }
}
// MARK: - Preview Data
private enum PreviewData {
static let context: NSManagedObjectContext = {
let container = NSPersistentContainer(name: "MyQrCode")
let description = NSPersistentStoreDescription()
description.type = NSInMemoryStoreType
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { _, _ in }
return container.viewContext
}()
private static func makeBaseItem(in context: NSManagedObjectContext, content: String, barcodeType: BarcodeType, favorite: Bool = false) -> HistoryItem {
let item = HistoryItem(context: context)
item.id = UUID()
item.content = content
item.dataType = DataType.barcode.rawValue
item.dataSource = DataSource.created.rawValue
item.createdAt = Date()
item.isFavorite = favorite
item.barcodeType = barcodeType.rawValue
return item
}
static func ean13Sample(in context: NSManagedObjectContext) -> HistoryItem {
let content = "1234567890128"
return makeBaseItem(in: context, content: content, barcodeType: .ean13, favorite: true)
}
static func code128Sample(in context: NSManagedObjectContext) -> HistoryItem {
let content = "ABC123"
return makeBaseItem(in: context, content: content, barcodeType: .code128)
}
}

@ -578,13 +578,18 @@ struct HistoryItemRow: View {
}
}
.background(
//
//
Group {
if item.dataType == DataType.qrcode.rawValue {
NavigationLink(
destination: QRCodeDetailView(historyItem: item),
label: { EmptyView() }
)
} else if item.dataType == DataType.barcode.rawValue {
NavigationLink(
destination: BarcodeDetailView(historyItem: item),
label: { EmptyView() }
)
}
}
)

Loading…
Cancel
Save