Refactor QRCodeColor enum in QRCodeStyleModels to remove gradient color cases, simplifying color options for QR code customization. Update ScannerView to enhance image picker functionality with new parameters for image processing. Revise QRCodeStyleView to streamline image selection and processing, including automatic resizing and compression of selected images, improving overall user experience in logo customization.

main
v504 2 months ago
parent 9bd7effd4c
commit 3d48275c62

@ -24,20 +24,6 @@ enum QRCodeColor: String, CaseIterable, Hashable {
case olive = "olive" case olive = "olive"
case silver = "silver" case silver = "silver"
//
case gradientRed = "gradientRed"
case gradientBlue = "gradientBlue"
case gradientGreen = "gradientGreen"
case gradientPurple = "gradientPurple"
case gradientOrange = "gradientOrange"
case gradientPink = "gradientPink"
case gradientYellow = "gradientYellow"
case gradientCyan = "gradientCyan"
case gradientMagenta = "gradientMagenta"
case gradientTeal = "gradientTeal"
case gradientIndigo = "gradientIndigo"
case gradientLime = "gradientLime"
var color: Color { var color: Color {
switch self { switch self {
case .black: return Color(red: 0, green: 0, blue: 0) case .black: return Color(red: 0, green: 0, blue: 0)
@ -60,18 +46,6 @@ enum QRCodeColor: String, CaseIterable, Hashable {
case .maroon: return Color(red: 0.5, green: 0, blue: 0) case .maroon: return Color(red: 0.5, green: 0, blue: 0)
case .olive: return Color(red: 0.5, green: 0.5, blue: 0) case .olive: return Color(red: 0.5, green: 0.5, blue: 0)
case .silver: return Color(red: 0.75, green: 0.75, blue: 0.75) case .silver: return Color(red: 0.75, green: 0.75, blue: 0.75)
case .gradientRed: return Color(red: 1, green: 0, blue: 0)
case .gradientBlue: return Color(red: 0, green: 0, blue: 1)
case .gradientGreen: return Color(red: 0, green: 1, blue: 0)
case .gradientPurple: return Color(red: 0.5, green: 0, blue: 0.5)
case .gradientOrange: return Color(red: 1, green: 0.5, blue: 0)
case .gradientPink: return Color(red: 1, green: 0.75, blue: 0.8)
case .gradientYellow: return Color(red: 1, green: 1, blue: 0)
case .gradientCyan: return Color(red: 0, green: 1, blue: 1)
case .gradientMagenta: return Color(red: 1, green: 0, blue: 1)
case .gradientTeal: return Color(red: 0, green: 0.5, blue: 0.5)
case .gradientIndigo: return Color(red: 0.3, green: 0, blue: 0.5)
case .gradientLime: return Color(red: 0.5, green: 1, blue: 0)
} }
} }
@ -80,7 +54,7 @@ enum QRCodeColor: String, CaseIterable, Hashable {
} }
static var foregroundColors: [QRCodeColor] { static var foregroundColors: [QRCodeColor] {
return [.black, .red, .blue, .green, .purple, .orange, .pink, .brown, .navy, .teal, .indigo, .maroon, .olive, .gradientRed, .gradientBlue, .gradientGreen, .gradientPurple, .gradientOrange, .gradientPink, .gradientYellow, .gradientCyan, .gradientMagenta, .gradientTeal, .gradientIndigo, .gradientLime] return [.black, .red, .blue, .green, .purple, .orange, .pink, .brown, .navy, .teal, .indigo, .maroon, .olive]
} }
static var backgroundColors: [QRCodeColor] { static var backgroundColors: [QRCodeColor] {

@ -81,7 +81,10 @@ struct ScannerView: View {
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(false) .navigationBarBackButtonHidden(false)
.sheet(isPresented: $showImagePicker) { .sheet(isPresented: $showImagePicker) {
ImagePicker(onImageSelected: handleImageDecodeResult) ImagePicker(
onImageSelected: handleImageDecodeResult,
shouldProcessImage: false
)
} }
.toolbar { .toolbar {
ToolbarItem(placement: .navigationBarLeading) { ToolbarItem(placement: .navigationBarLeading) {
@ -515,46 +518,7 @@ struct ScannerView: View {
} }
} }
// MARK: - iOS 15
struct ImagePicker: UIViewControllerRepresentable {
let onImageSelected: (UIImage) -> Void
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = .photoLibrary
picker.allowsEditing = true
picker.videoQuality = .typeHigh
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let editedImage = info[.editedImage] as? UIImage {
parent.onImageSelected(editedImage)
} else if let image = info[.originalImage] as? UIImage {
parent.onImageSelected(image)
}
picker.dismiss(animated: true)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true)
}
}
}
// MARK: - // MARK: -
struct DecodeFailureOverlay: View { struct DecodeFailureOverlay: View {

@ -51,11 +51,8 @@ struct QRCodeStyleView: View {
// Logo // Logo
@State private var selectedLogo: QRCodeLogo? = nil @State private var selectedLogo: QRCodeLogo? = nil
@State private var customLogoImage: UIImage? = nil @State private var customLogoImage: UIImage? = nil
@State private var photoPickerItem: Any? = nil
@State private var photoLibraryAccessGranted = false @State private var photoLibraryAccessGranted = false
@State private var showingImagePicker = false @State private var showingImagePicker = false
@State private var showingImageCropper = false
@State private var imageToCrop: UIImage? = nil
// //
@State private var qrCodeImage: UIImage? @State private var qrCodeImage: UIImage?
@ -95,7 +92,7 @@ struct QRCodeStyleView: View {
let cgImage = customLogoImage.cgImage { let cgImage = customLogoImage.cgImage {
// 使Logo // 使Logo
print("应用自定义LogoCGImage大小: \(cgImage.width) x \(cgImage.height)") print("应用自定义LogoCGImage大小: \(cgImage.width) x \(cgImage.height)")
d.logoTemplate = QRCode.LogoTemplate.CircleCenter(image: cgImage) d.logoTemplate = QRCode.LogoTemplate.CircleCenter(image: cgImage, inset: 0)
} else if let selectedLogo = selectedLogo, } else if let selectedLogo = selectedLogo,
let logoImage = selectedLogo.image, let logoImage = selectedLogo.image,
let cgImage = logoImage.cgImage { let cgImage = logoImage.cgImage {
@ -131,19 +128,14 @@ struct QRCodeStyleView: View {
checkPhotoLibraryPermission() checkPhotoLibraryPermission()
} }
.sheet(isPresented: $showingImagePicker) { .sheet(isPresented: $showingImagePicker) {
ImagePicker { image in ImagePicker(
imageToCrop = image onImageSelected: { image in
showingImageCropper = true customLogoImage = image
}
}
.sheet(isPresented: $showingImageCropper) {
if let imageToCrop = imageToCrop {
ImageCropperView(image: imageToCrop) { croppedImage in
customLogoImage = croppedImage
selectedLogo = nil // Logo selectedLogo = nil // Logo
self.imageToCrop = nil },
} shouldProcessImage: true,
} targetSize: CGSize(width: 80, height: 80)
)
} }
} }
@ -640,193 +632,187 @@ struct QRCodeStyleView: View {
} }
} }
// MARK: -
struct ImageCropperView: View {
let image: UIImage // MARK: -
let onCropComplete: (UIImage) -> Void struct ImagePicker: UIViewControllerRepresentable {
let onImageSelected: (UIImage) -> Void
let shouldProcessImage: Bool
let targetSize: CGSize?
@Environment(\.dismiss) private var dismiss init(onImageSelected: @escaping (UIImage) -> Void, shouldProcessImage: Bool = false, targetSize: CGSize? = nil) {
@State private var scale: CGFloat = 1.0 self.onImageSelected = onImageSelected
@State private var lastScale: CGFloat = 1.0 self.shouldProcessImage = shouldProcessImage
@State private var offset: CGSize = .zero self.targetSize = targetSize
@State private var lastOffset: CGSize = .zero }
var body: some View { func makeUIViewController(context: Context) -> UIImagePickerController {
NavigationView { let picker = UIImagePickerController()
GeometryReader { geometry in picker.delegate = context.coordinator
ZStack { picker.sourceType = .photoLibrary
Color.black picker.allowsEditing = false
.ignoresSafeArea() return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.originalImage] as? UIImage {
let finalImage: UIImage
if parent.shouldProcessImage, let targetSize = parent.targetSize {
//
finalImage = processImageToSquare(image: image, targetSize: targetSize)
VStack { //
// let memorySize = calculateImageMemorySize(image: finalImage)
ZStack { print("📊 最终自定义图片内存大小: \(memorySize)")
// } else {
Image(uiImage: image) // 使
.resizable() finalImage = image
.aspectRatio(contentMode: .fit)
.scaleEffect(scale)
.offset(offset)
.gesture(
SimultaneousGesture(
MagnificationGesture()
.onChanged { value in
let delta = value / lastScale
lastScale = value
scale = min(max(scale * delta, 0.5), 3.0)
}
.onEnded { _ in
lastScale = 1.0
},
DragGesture()
.onChanged { value in
let delta = CGSize(
width: value.translation.width - lastOffset.width,
height: value.translation.height - lastOffset.height
)
lastOffset = value.translation
offset = CGSize(
width: offset.width + delta.width,
height: offset.height + delta.height
)
}
.onEnded { _ in
lastOffset = .zero
}
)
)
//
CropOverlay()
}
.frame(height: geometry.size.width) //
.clipped()
Spacer()
//
Text("拖动和缩放来选择圆形Logo区域")
.foregroundColor(.white)
.font(.caption)
.padding(.bottom, 20)
}
}
}
.navigationTitle("裁剪圆形Logo")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("取消") {
dismiss()
}
.foregroundColor(.white)
} }
ToolbarItem(placement: .navigationBarTrailing) { parent.onImageSelected(finalImage)
Button("完成") {
let croppedImage = cropImage()
onCropComplete(croppedImage)
dismiss()
}
.foregroundColor(.white)
.font(.system(size: 16, weight: .semibold))
}
} }
picker.dismiss(animated: true)
} }
}
private func cropImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 80, height: 80))
return renderer.image { context in func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
// picker.dismiss(animated: true)
let imageSize = image.size }
let viewSize = CGSize(width: 80, height: 80)
// 5KB
private func processImageToSquare(image: UIImage, targetSize: CGSize) -> UIImage {
let originalSize = image.size
// //
let imageAspectRatio = imageSize.width / imageSize.height let squareSize = min(originalSize.width, originalSize.height)
let viewAspectRatio = viewSize.width / viewSize.height
let scaledImageSize: CGSize //
let scaledImageOffset: CGPoint let cropX = (originalSize.width - squareSize) / 2
let cropY = (originalSize.height - squareSize) / 2
let cropRect = CGRect(x: cropX, y: cropY, width: squareSize, height: squareSize)
if imageAspectRatio > viewAspectRatio { //
// guard let cgImage = image.cgImage?.cropping(to: cropRect) else {
scaledImageSize = CGSize(width: viewSize.height * imageAspectRatio, height: viewSize.height) return image
scaledImageOffset = CGPoint(x: (viewSize.width - scaledImageSize.width) / 2, y: 0)
} else {
//
scaledImageSize = CGSize(width: viewSize.width, height: viewSize.width / imageAspectRatio)
scaledImageOffset = CGPoint(x: 0, y: (viewSize.height - scaledImageSize.height) / 2)
} }
// // UIImage
let finalImageSize = CGSize( let croppedImage = UIImage(cgImage: cgImage)
width: scaledImageSize.width * scale,
height: scaledImageSize.height * scale
)
let finalImageOffset = CGPoint( //
x: scaledImageOffset.x + offset.width, let renderer = UIGraphicsImageRenderer(size: targetSize)
y: scaledImageOffset.y + offset.height let scaledImage = renderer.image { context in
) croppedImage.draw(in: CGRect(origin: .zero, size: targetSize))
}
// // 5KB
let cropRect = CGRect( return compressImageToTargetSize(scaledImage, targetSizeInKB: 5.0)
x: -finalImageOffset.x, }
y: -finalImageOffset.y,
width: finalImageSize.width, //
height: finalImageSize.height private func compressImageToTargetSize(_ image: UIImage, targetSizeInKB: Double) -> UIImage {
) let targetSizeInBytes = Int64(targetSizeInKB * 1024)
//
let compressionQualities: [CGFloat] = [0.8, 0.6, 0.4, 0.2, 0.1, 0.05]
// - for quality in compressionQualities {
let circlePath = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 80, height: 80)) if let imageData = image.jpegData(compressionQuality: quality) {
circlePath.addClip() let dataSize = Int64(imageData.count)
if dataSize <= targetSizeInBytes {
// UIImage
if let compressedImage = UIImage(data: imageData) {
print("✅ 图片压缩成功: \(dataSize) bytes (质量: \(quality))")
return compressedImage
}
}
}
}
// // JPEG
image.draw(in: cropRect) print("⚠️ JPEG压缩后仍超过目标大小尝试缩小尺寸")
return compressImageByReducingSize(image, targetSizeInBytes: targetSizeInBytes)
} }
}
} //
private func compressImageByReducingSize(_ image: UIImage, targetSizeInBytes: Int64) -> UIImage {
// MARK: - let originalSize = image.size
struct CropOverlay: View { let originalWidth = originalSize.width
var body: some View { let originalHeight = originalSize.height
GeometryReader { geometry in
ZStack { //
// let currentMemorySize = Int64(originalWidth * originalHeight * 4) // RGBA
Color.black.opacity(0.5)
.mask( //
Rectangle() let scaleFactor = sqrt(Double(targetSizeInBytes) / Double(currentMemorySize))
.overlay( let newWidth = max(originalWidth * CGFloat(scaleFactor), 40) // 40
Circle() let newHeight = max(originalHeight * CGFloat(scaleFactor), 40)
.frame(
width: min(geometry.size.width, geometry.size.height) * 0.8, let newSize = CGSize(width: newWidth, height: newHeight)
height: min(geometry.size.width, geometry.size.height) * 0.8
) //
.blendMode(.destinationOut) let renderer = UIGraphicsImageRenderer(size: newSize)
) let resizedImage = renderer.image { context in
) image.draw(in: CGRect(origin: .zero, size: newSize))
}
//
Circle() // JPEG
.stroke(Color.white, lineWidth: 2) if let imageData = resizedImage.jpegData(compressionQuality: 0.3) {
.frame( let finalSize = Int64(imageData.count)
width: min(geometry.size.width, geometry.size.height) * 0.8, print("✅ 通过缩小尺寸压缩成功: \(finalSize) bytes (新尺寸: \(newWidth) x \(newHeight))")
height: min(geometry.size.width, geometry.size.height) * 0.8
)
// if let finalImage = UIImage(data: imageData) {
ForEach(0..<4) { corner in return finalImage
Circle()
.fill(Color.white)
.frame(width: 6, height: 6)
.offset(
x: corner % 2 == 0 ? -min(geometry.size.width, geometry.size.height) * 0.4 : min(geometry.size.width, geometry.size.height) * 0.4,
y: corner < 2 ? -min(geometry.size.width, geometry.size.height) * 0.4 : min(geometry.size.width, geometry.size.height) * 0.4
)
} }
} }
//
print("⚠️ 无法压缩到目标大小,返回最小尺寸图片")
let minSize = CGSize(width: 40, height: 40)
let rendererMin = UIGraphicsImageRenderer(size: minSize)
return rendererMin.image { context in
image.draw(in: CGRect(origin: .zero, size: minSize))
}
}
//
private func calculateImageMemorySize(image: UIImage) -> String {
guard let cgImage = image.cgImage else {
return "无法计算"
}
let width = cgImage.width
let height = cgImage.height
let bitsPerComponent = cgImage.bitsPerComponent
let bytesPerRow = cgImage.bytesPerRow
let colorSpace = cgImage.colorSpace
//
let memorySizeInBytes = height * bytesPerRow
//
let formatter = ByteCountFormatter()
formatter.allowedUnits = [.useKB, .useMB]
formatter.countStyle = .memory
let memorySizeString = formatter.string(fromByteCount: Int64(memorySizeInBytes))
//
return "\(memorySizeString) (\(width) x \(height), \(bitsPerComponent) bits/component, \(bytesPerRow) bytes/row)"
} }
} }
} }

Loading…
Cancel
Save