You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

397 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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? {
// Bundle
if let path = Bundle.main.path(forResource: name, ofType: "png", inDirectory: "Resources/dots") {
return UIImage(contentsOfFile: path)
}
if let path = Bundle.main.path(forResource: name, ofType: "png", inDirectory: "Resources/eyes") {
return UIImage(contentsOfFile: path)
}
if let path = Bundle.main.path(forResource: name, ofType: "png", inDirectory: "Resources/logos") {
return UIImage(contentsOfFile: path)
}
return nil
}
}
// MARK: -
#Preview {
QRCodeStyleView(qrCodeContent: "https://www.example.com")
}