|
|
import SwiftUI
|
|
|
import QRCode
|
|
|
import CoreData
|
|
|
|
|
|
// MARK: - 自定义二维码样式界面
|
|
|
struct QRCodeStyleView: View {
|
|
|
let qrCodeContent: String
|
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
@StateObject private var coreDataManager = CoreDataManager.shared
|
|
|
|
|
|
// 颜色选择
|
|
|
@State private var selectedForegroundColor: QRCodeColor = .black
|
|
|
@State private var selectedBackgroundColor: QRCodeColor = .white
|
|
|
|
|
|
// 点类型选择
|
|
|
@State private var selectedDotType: QRCodeDotType = .square
|
|
|
|
|
|
// 眼睛类型选择
|
|
|
@State private var selectedEyeType: QRCodeEyeType = .square
|
|
|
|
|
|
// Logo选择
|
|
|
@State private var selectedLogo: QRCodeLogo? = nil
|
|
|
|
|
|
// 生成的二维码图片
|
|
|
@State private var qrCodeImage: UIImage?
|
|
|
@State private var isLoading = false
|
|
|
|
|
|
// 创建QRCode文档
|
|
|
private func createQRCodeDocument() -> QRCode.Document {
|
|
|
let d = try! QRCode.Document(engine: QRCodeEngineExternal())
|
|
|
|
|
|
// 使用传入的二维码内容
|
|
|
d.utf8String = qrCodeContent
|
|
|
|
|
|
// 设置背景色
|
|
|
d.design.backgroundColor(selectedBackgroundColor.cgColor)
|
|
|
|
|
|
// 设置眼睛样式
|
|
|
d.design.style.eye = QRCode.FillStyle.Solid(selectedForegroundColor.cgColor)
|
|
|
d.design.style.eyeBackground = selectedBackgroundColor.cgColor
|
|
|
|
|
|
// 设置点样式
|
|
|
d.design.shape.onPixels = selectedDotType.pixelShape
|
|
|
d.design.style.onPixels = QRCode.FillStyle.Solid(selectedForegroundColor.cgColor)
|
|
|
d.design.style.onPixelsBackground = selectedBackgroundColor.cgColor
|
|
|
|
|
|
d.design.shape.offPixels = selectedDotType.pixelShape
|
|
|
d.design.style.offPixels = QRCode.FillStyle.Solid(selectedBackgroundColor.cgColor)
|
|
|
d.design.style.offPixelsBackground = selectedBackgroundColor.cgColor
|
|
|
|
|
|
// 设置眼睛形状
|
|
|
d.design.shape.eye = selectedEyeType.eyeShape
|
|
|
|
|
|
// 如果有选择的Logo,设置背景图片
|
|
|
if let selectedLogo = selectedLogo {
|
|
|
// 这里可以添加Logo图片设置
|
|
|
// d.design.style.background = QRCode.FillStyle.Image(selectedLogo.image)
|
|
|
}
|
|
|
|
|
|
return d
|
|
|
}
|
|
|
|
|
|
var body: some View {
|
|
|
VStack(spacing: 0) {
|
|
|
// 二维码预览区域
|
|
|
qrCodePreviewSection
|
|
|
|
|
|
// 样式选择区域
|
|
|
styleSelectionSection
|
|
|
}
|
|
|
.navigationTitle("自定义样式")
|
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
|
.toolbar {
|
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
|
Button("保存") {
|
|
|
saveQRCode()
|
|
|
}
|
|
|
.font(.system(size: 16, weight: .semibold))
|
|
|
}
|
|
|
}
|
|
|
.onAppear {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 二维码预览区域
|
|
|
private var qrCodePreviewSection: some View {
|
|
|
VStack(spacing: 16) {
|
|
|
QRCodeDocumentUIView(document: createQRCodeDocument())
|
|
|
.frame(width: 300, height: 300)
|
|
|
}
|
|
|
.padding()
|
|
|
.background(Color(.systemBackground))
|
|
|
}
|
|
|
|
|
|
// MARK: - 样式选择区域
|
|
|
private var styleSelectionSection: some View {
|
|
|
ScrollView {
|
|
|
VStack(spacing: 24) {
|
|
|
// 前景色选择
|
|
|
colorSelectionSection(
|
|
|
title: "前景色",
|
|
|
colors: QRCodeColor.foregroundColors,
|
|
|
selectedColor: $selectedForegroundColor
|
|
|
)
|
|
|
|
|
|
// 背景色选择
|
|
|
colorSelectionSection(
|
|
|
title: "背景色",
|
|
|
colors: QRCodeColor.backgroundColors,
|
|
|
selectedColor: $selectedBackgroundColor
|
|
|
)
|
|
|
|
|
|
// 点类型选择
|
|
|
dotTypeSelectionSection
|
|
|
|
|
|
// 眼睛类型选择
|
|
|
eyeTypeSelectionSection
|
|
|
|
|
|
// Logo选择
|
|
|
logoSelectionSection
|
|
|
}
|
|
|
.padding()
|
|
|
}
|
|
|
.background(Color(.systemGroupedBackground))
|
|
|
}
|
|
|
|
|
|
// MARK: - 颜色选择区域
|
|
|
private func colorSelectionSection(
|
|
|
title: String,
|
|
|
colors: [QRCodeColor],
|
|
|
selectedColor: Binding<QRCodeColor>
|
|
|
) -> some View {
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
Text(title)
|
|
|
.font(.headline)
|
|
|
.foregroundColor(.primary)
|
|
|
|
|
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 6), spacing: 12) {
|
|
|
ForEach(colors, id: \.self) { color in
|
|
|
Button(action: {
|
|
|
selectedColor.wrappedValue = color
|
|
|
}) {
|
|
|
RoundedRectangle(cornerRadius: 8)
|
|
|
.fill(color.color)
|
|
|
.frame(height: 40)
|
|
|
.overlay(
|
|
|
RoundedRectangle(cornerRadius: 8)
|
|
|
.stroke(selectedColor.wrappedValue == color ? Color.blue : Color.clear, lineWidth: 3)
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 点类型选择区域
|
|
|
private var dotTypeSelectionSection: some View {
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
Text("点类型")
|
|
|
.font(.headline)
|
|
|
.foregroundColor(.primary)
|
|
|
|
|
|
ScrollView(.horizontal, showsIndicators: false) {
|
|
|
HStack(spacing: 12) {
|
|
|
ForEach(QRCodeDotType.allCases, id: \.self) { dotType in
|
|
|
Button(action: {
|
|
|
selectedDotType = dotType
|
|
|
}) {
|
|
|
VStack(spacing: 8) {
|
|
|
if let image = loadImage(named: dotType.thumbnailName) {
|
|
|
Image(uiImage: image)
|
|
|
.resizable()
|
|
|
.aspectRatio(contentMode: .fit)
|
|
|
.frame(width: 40, height: 40)
|
|
|
.background(Color.white)
|
|
|
.cornerRadius(8)
|
|
|
} else {
|
|
|
RoundedRectangle(cornerRadius: 8)
|
|
|
.fill(Color.gray.opacity(0.3))
|
|
|
.frame(width: 40, height: 40)
|
|
|
.overlay(
|
|
|
Text("?")
|
|
|
.font(.caption)
|
|
|
.foregroundColor(.secondary)
|
|
|
)
|
|
|
}
|
|
|
|
|
|
Text(dotType.displayName)
|
|
|
.font(.caption)
|
|
|
.foregroundColor(.primary)
|
|
|
}
|
|
|
.padding(8)
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 12)
|
|
|
.fill(selectedDotType == dotType ? Color.blue.opacity(0.1) : Color.clear)
|
|
|
.overlay(
|
|
|
RoundedRectangle(cornerRadius: 12)
|
|
|
.stroke(selectedDotType == dotType ? Color.blue : Color.clear, lineWidth: 2)
|
|
|
)
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.padding(.horizontal)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 眼睛类型选择区域
|
|
|
private var eyeTypeSelectionSection: some View {
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
Text("眼睛类型")
|
|
|
.font(.headline)
|
|
|
.foregroundColor(.primary)
|
|
|
|
|
|
ScrollView(.horizontal, showsIndicators: false) {
|
|
|
HStack(spacing: 12) {
|
|
|
ForEach(QRCodeEyeType.allCases, id: \.self) { eyeType in
|
|
|
Button(action: {
|
|
|
selectedEyeType = eyeType
|
|
|
}) {
|
|
|
VStack(spacing: 8) {
|
|
|
if let image = loadImage(named: eyeType.thumbnailName) {
|
|
|
Image(uiImage: image)
|
|
|
.resizable()
|
|
|
.aspectRatio(contentMode: .fit)
|
|
|
.frame(width: 40, height: 40)
|
|
|
.background(Color.white)
|
|
|
.cornerRadius(8)
|
|
|
} else {
|
|
|
RoundedRectangle(cornerRadius: 8)
|
|
|
.fill(Color.gray.opacity(0.3))
|
|
|
.frame(width: 40, height: 40)
|
|
|
.overlay(
|
|
|
Text("?")
|
|
|
.font(.caption)
|
|
|
.foregroundColor(.secondary)
|
|
|
)
|
|
|
}
|
|
|
|
|
|
Text(eyeType.displayName)
|
|
|
.font(.caption)
|
|
|
.foregroundColor(.primary)
|
|
|
}
|
|
|
.padding(8)
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 12)
|
|
|
.fill(selectedEyeType == eyeType ? Color.blue.opacity(0.1) : Color.clear)
|
|
|
.overlay(
|
|
|
RoundedRectangle(cornerRadius: 12)
|
|
|
.stroke(selectedEyeType == eyeType ? Color.blue : Color.clear, lineWidth: 2)
|
|
|
)
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.padding(.horizontal)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - Logo选择区域
|
|
|
private var logoSelectionSection: some View {
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
|
Text("Logo")
|
|
|
.font(.headline)
|
|
|
.foregroundColor(.primary)
|
|
|
|
|
|
ScrollView(.horizontal, showsIndicators: false) {
|
|
|
HStack(spacing: 12) {
|
|
|
// 无Logo选项
|
|
|
Button(action: {
|
|
|
selectedLogo = nil
|
|
|
}) {
|
|
|
VStack(spacing: 8) {
|
|
|
RoundedRectangle(cornerRadius: 8)
|
|
|
.fill(Color.gray.opacity(0.3))
|
|
|
.frame(width: 40, height: 40)
|
|
|
.overlay(
|
|
|
Text("无")
|
|
|
.font(.caption)
|
|
|
.foregroundColor(.secondary)
|
|
|
)
|
|
|
|
|
|
Text("无Logo")
|
|
|
.font(.caption)
|
|
|
.foregroundColor(.primary)
|
|
|
}
|
|
|
.padding(8)
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 12)
|
|
|
.fill(selectedLogo == nil ? Color.blue.opacity(0.1) : Color.clear)
|
|
|
.overlay(
|
|
|
RoundedRectangle(cornerRadius: 12)
|
|
|
.stroke(selectedLogo == nil ? Color.blue : Color.clear, lineWidth: 2)
|
|
|
)
|
|
|
)
|
|
|
}
|
|
|
|
|
|
// Logo选项
|
|
|
ForEach(QRCodeLogo.allCases, id: \.self) { logo in
|
|
|
Button(action: {
|
|
|
selectedLogo = logo
|
|
|
}) {
|
|
|
VStack(spacing: 8) {
|
|
|
if let image = loadImage(named: logo.thumbnailName) {
|
|
|
Image(uiImage: image)
|
|
|
.resizable()
|
|
|
.aspectRatio(contentMode: .fit)
|
|
|
.frame(width: 40, height: 40)
|
|
|
.background(Color.white)
|
|
|
.cornerRadius(8)
|
|
|
} else {
|
|
|
RoundedRectangle(cornerRadius: 8)
|
|
|
.fill(Color.gray.opacity(0.3))
|
|
|
.frame(width: 40, height: 40)
|
|
|
.overlay(
|
|
|
Text("?")
|
|
|
.font(.caption)
|
|
|
.foregroundColor(.secondary)
|
|
|
)
|
|
|
}
|
|
|
|
|
|
Text(logo.displayName)
|
|
|
.font(.caption)
|
|
|
.foregroundColor(.primary)
|
|
|
}
|
|
|
.padding(8)
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 12)
|
|
|
.fill(selectedLogo == logo ? Color.blue.opacity(0.1) : Color.clear)
|
|
|
.overlay(
|
|
|
RoundedRectangle(cornerRadius: 12)
|
|
|
.stroke(selectedLogo == logo ? Color.blue : Color.clear, lineWidth: 2)
|
|
|
)
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.padding(.horizontal)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 保存二维码
|
|
|
private func saveQRCode() {
|
|
|
guard let qrCodeImage = qrCodeImage else { return }
|
|
|
|
|
|
// 保存到相册
|
|
|
UIImageWriteToSavedPhotosAlbum(qrCodeImage, nil, nil, nil)
|
|
|
|
|
|
// 保存到历史记录
|
|
|
saveToHistory()
|
|
|
|
|
|
dismiss()
|
|
|
}
|
|
|
|
|
|
// MARK: - 保存到历史记录
|
|
|
private func saveToHistory() {
|
|
|
let context = coreDataManager.container.viewContext
|
|
|
let historyItem = HistoryItem(context: context)
|
|
|
historyItem.id = UUID()
|
|
|
historyItem.dataType = DataType.qrcode.rawValue
|
|
|
historyItem.dataSource = DataSource.created.rawValue
|
|
|
historyItem.createdAt = Date()
|
|
|
historyItem.isFavorite = false
|
|
|
historyItem.qrCodeType = "custom"
|
|
|
historyItem.content = qrCodeContent
|
|
|
|
|
|
do {
|
|
|
try context.save()
|
|
|
} catch {
|
|
|
print("保存到历史记录失败:\(error.localizedDescription)")
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 辅助函数
|
|
|
private func loadImage(named name: String) -> UIImage? {
|
|
|
// 方法1: 尝试从Bundle中直接加载
|
|
|
if let image = UIImage(named: name) {
|
|
|
return image
|
|
|
}
|
|
|
|
|
|
// 方法2: 尝试从Resources子目录加载
|
|
|
let subdirectories = ["dots", "eyes", "logos"]
|
|
|
for subdirectory in subdirectories {
|
|
|
if let path = Bundle.main.path(forResource: name, ofType: "png", inDirectory: "Resources/\(subdirectory)") {
|
|
|
return UIImage(contentsOfFile: path)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 方法3: 尝试从Bundle的Resources目录加载
|
|
|
if let bundlePath = Bundle.main.path(forResource: "Resources", ofType: nil) {
|
|
|
for subdirectory in subdirectories {
|
|
|
if let imagePath = Bundle.main.path(forResource: name, ofType: "png", inDirectory: subdirectory) {
|
|
|
return UIImage(contentsOfFile: imagePath)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 方法4: 尝试从Assets.xcassets加载
|
|
|
if let image = UIImage(named: name, in: Bundle.main, with: nil) {
|
|
|
return image
|
|
|
}
|
|
|
|
|
|
// 方法5: 尝试从Bundle根目录加载
|
|
|
if let path = Bundle.main.path(forResource: name, ofType: "png") {
|
|
|
return UIImage(contentsOfFile: path)
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// MARK: - 预览
|
|
|
#Preview {
|
|
|
QRCodeStyleView(qrCodeContent: "https://www.example.com")
|
|
|
}
|