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
let onCropComplete: (UIImage) -> Void
@Environment(\.dismiss) private var dismiss
@State private var scale: CGFloat = 1.0
@State private var lastScale: CGFloat = 1.0
@State private var offset: CGSize = .zero
@State private var lastOffset: CGSize = .zero
var body: some View { // MARK: -
NavigationView { struct ImagePicker: UIViewControllerRepresentable {
GeometryReader { geometry in let onImageSelected: (UIImage) -> Void
ZStack { let shouldProcessImage: Bool
Color.black let targetSize: CGSize?
.ignoresSafeArea()
init(onImageSelected: @escaping (UIImage) -> Void, shouldProcessImage: Bool = false, targetSize: CGSize? = nil) {
VStack { self.onImageSelected = onImageSelected
// self.shouldProcessImage = shouldProcessImage
ZStack { self.targetSize = targetSize
//
Image(uiImage: image)
.resizable()
.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
} }
)
)
// func makeUIViewController(context: Context) -> UIImagePickerController {
CropOverlay() let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = .photoLibrary
picker.allowsEditing = false
return picker
} }
.frame(height: geometry.size.width) //
.clipped()
Spacer() func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
// func makeCoordinator() -> Coordinator {
Text("拖动和缩放来选择圆形Logo区域") Coordinator(self)
.foregroundColor(.white)
.font(.caption)
.padding(.bottom, 20)
} }
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)
//
let memorySize = calculateImageMemorySize(image: finalImage)
print("📊 最终自定义图片内存大小: \(memorySize)")
} else {
// 使
finalImage = image
} }
.navigationTitle("裁剪圆形Logo")
.navigationBarTitleDisplayMode(.inline) parent.onImageSelected(finalImage)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("取消") {
dismiss()
} }
.foregroundColor(.white) picker.dismiss(animated: true)
} }
ToolbarItem(placement: .navigationBarTrailing) { func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
Button("完成") { picker.dismiss(animated: true)
let croppedImage = cropImage()
onCropComplete(croppedImage)
dismiss()
}
.foregroundColor(.white)
.font(.system(size: 16, weight: .semibold))
} }
// 5KB
private func processImageToSquare(image: UIImage, targetSize: CGSize) -> UIImage {
let originalSize = image.size
//
let squareSize = min(originalSize.width, originalSize.height)
//
let cropX = (originalSize.width - squareSize) / 2
let cropY = (originalSize.height - squareSize) / 2
let cropRect = CGRect(x: cropX, y: cropY, width: squareSize, height: squareSize)
//
guard let cgImage = image.cgImage?.cropping(to: cropRect) else {
return image
} }
// UIImage
let croppedImage = UIImage(cgImage: cgImage)
//
let renderer = UIGraphicsImageRenderer(size: targetSize)
let scaledImage = renderer.image { context in
croppedImage.draw(in: CGRect(origin: .zero, size: targetSize))
} }
// 5KB
return compressImageToTargetSize(scaledImage, targetSizeInKB: 5.0)
} }
private func cropImage() -> UIImage { //
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 80, height: 80)) private func compressImageToTargetSize(_ image: UIImage, targetSizeInKB: Double) -> UIImage {
let targetSizeInBytes = Int64(targetSizeInKB * 1024)
return renderer.image { context in //
// let compressionQualities: [CGFloat] = [0.8, 0.6, 0.4, 0.2, 0.1, 0.05]
let imageSize = image.size
let viewSize = CGSize(width: 80, height: 80)
// for quality in compressionQualities {
let imageAspectRatio = imageSize.width / imageSize.height if let imageData = image.jpegData(compressionQuality: quality) {
let viewAspectRatio = viewSize.width / viewSize.height let dataSize = Int64(imageData.count)
let scaledImageSize: CGSize if dataSize <= targetSizeInBytes {
let scaledImageOffset: CGPoint // UIImage
if let compressedImage = UIImage(data: imageData) {
print("✅ 图片压缩成功: \(dataSize) bytes (质量: \(quality))")
return compressedImage
}
}
}
}
if imageAspectRatio > viewAspectRatio { // JPEG
// print("⚠️ JPEG压缩后仍超过目标大小尝试缩小尺寸")
scaledImageSize = CGSize(width: viewSize.height * imageAspectRatio, height: viewSize.height) return compressImageByReducingSize(image, targetSizeInBytes: targetSizeInBytes)
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)
} }
// //
let finalImageSize = CGSize( private func compressImageByReducingSize(_ image: UIImage, targetSizeInBytes: Int64) -> UIImage {
width: scaledImageSize.width * scale, let originalSize = image.size
height: scaledImageSize.height * scale let originalWidth = originalSize.width
) let originalHeight = originalSize.height
let finalImageOffset = CGPoint( //
x: scaledImageOffset.x + offset.width, let currentMemorySize = Int64(originalWidth * originalHeight * 4) // RGBA
y: scaledImageOffset.y + offset.height
)
// //
let cropRect = CGRect( let scaleFactor = sqrt(Double(targetSizeInBytes) / Double(currentMemorySize))
x: -finalImageOffset.x, let newWidth = max(originalWidth * CGFloat(scaleFactor), 40) // 40
y: -finalImageOffset.y, let newHeight = max(originalHeight * CGFloat(scaleFactor), 40)
width: finalImageSize.width,
height: finalImageSize.height
)
// - let newSize = CGSize(width: newWidth, height: newHeight)
let circlePath = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 80, height: 80))
circlePath.addClip()
// //
image.draw(in: cropRect) let renderer = UIGraphicsImageRenderer(size: newSize)
} let resizedImage = renderer.image { context in
} image.draw(in: CGRect(origin: .zero, size: newSize))
} }
// MARK: - // JPEG
struct CropOverlay: View { if let imageData = resizedImage.jpegData(compressionQuality: 0.3) {
var body: some View { let finalSize = Int64(imageData.count)
GeometryReader { geometry in print("✅ 通过缩小尺寸压缩成功: \(finalSize) bytes (新尺寸: \(newWidth) x \(newHeight))")
ZStack {
//
Color.black.opacity(0.5)
.mask(
Rectangle()
.overlay(
Circle()
.frame(
width: min(geometry.size.width, geometry.size.height) * 0.8,
height: min(geometry.size.width, geometry.size.height) * 0.8
)
.blendMode(.destinationOut)
)
)
// if let finalImage = UIImage(data: imageData) {
Circle() return finalImage
.stroke(Color.white, lineWidth: 2) }
.frame( }
width: min(geometry.size.width, geometry.size.height) * 0.8,
height: min(geometry.size.width, geometry.size.height) * 0.8
)
// //
ForEach(0..<4) { corner in print("⚠️ 无法压缩到目标大小,返回最小尺寸图片")
Circle() let minSize = CGSize(width: 40, height: 40)
.fill(Color.white) let rendererMin = UIGraphicsImageRenderer(size: minSize)
.frame(width: 6, height: 6) return rendererMin.image { context in
.offset( image.draw(in: CGRect(origin: .zero, size: minSize))
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
)
} }
//
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