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 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 {
switch self {
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 .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 .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] {
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] {

@ -81,7 +81,10 @@ struct ScannerView: View {
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(false)
.sheet(isPresented: $showImagePicker) {
ImagePicker(onImageSelected: handleImageDecodeResult)
ImagePicker(
onImageSelected: handleImageDecodeResult,
shouldProcessImage: false
)
}
.toolbar {
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: -
struct DecodeFailureOverlay: View {

@ -51,11 +51,8 @@ struct QRCodeStyleView: View {
// Logo
@State private var selectedLogo: QRCodeLogo? = nil
@State private var customLogoImage: UIImage? = nil
@State private var photoPickerItem: Any? = nil
@State private var photoLibraryAccessGranted = false
@State private var showingImagePicker = false
@State private var showingImageCropper = false
@State private var imageToCrop: UIImage? = nil
//
@State private var qrCodeImage: UIImage?
@ -95,7 +92,7 @@ struct QRCodeStyleView: View {
let cgImage = customLogoImage.cgImage {
// 使Logo
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,
let logoImage = selectedLogo.image,
let cgImage = logoImage.cgImage {
@ -131,19 +128,14 @@ struct QRCodeStyleView: View {
checkPhotoLibraryPermission()
}
.sheet(isPresented: $showingImagePicker) {
ImagePicker { image in
imageToCrop = image
showingImageCropper = true
}
}
.sheet(isPresented: $showingImageCropper) {
if let imageToCrop = imageToCrop {
ImageCropperView(image: imageToCrop) { croppedImage in
customLogoImage = croppedImage
ImagePicker(
onImageSelected: { image in
customLogoImage = image
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 {
NavigationView {
GeometryReader { geometry in
ZStack {
Color.black
.ignoresSafeArea()
VStack {
//
ZStack {
//
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
// MARK: -
struct ImagePicker: UIViewControllerRepresentable {
let onImageSelected: (UIImage) -> Void
let shouldProcessImage: Bool
let targetSize: CGSize?
init(onImageSelected: @escaping (UIImage) -> Void, shouldProcessImage: Bool = false, targetSize: CGSize? = nil) {
self.onImageSelected = onImageSelected
self.shouldProcessImage = shouldProcessImage
self.targetSize = targetSize
}
)
)
//
CropOverlay()
func makeUIViewController(context: Context) -> UIImagePickerController {
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) {}
//
Text("拖动和缩放来选择圆形Logo区域")
.foregroundColor(.white)
.font(.caption)
.padding(.bottom, 20)
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)
//
let memorySize = calculateImageMemorySize(image: finalImage)
print("📊 最终自定义图片内存大小: \(memorySize)")
} else {
// 使
finalImage = image
}
.navigationTitle("裁剪圆形Logo")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("取消") {
dismiss()
parent.onImageSelected(finalImage)
}
.foregroundColor(.white)
picker.dismiss(animated: true)
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("完成") {
let croppedImage = cropImage()
onCropComplete(croppedImage)
dismiss()
}
.foregroundColor(.white)
.font(.system(size: 16, weight: .semibold))
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true)
}
// 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 imageSize = image.size
let viewSize = CGSize(width: 80, height: 80)
//
let compressionQualities: [CGFloat] = [0.8, 0.6, 0.4, 0.2, 0.1, 0.05]
//
let imageAspectRatio = imageSize.width / imageSize.height
let viewAspectRatio = viewSize.width / viewSize.height
for quality in compressionQualities {
if let imageData = image.jpegData(compressionQuality: quality) {
let dataSize = Int64(imageData.count)
let scaledImageSize: CGSize
let scaledImageOffset: CGPoint
if dataSize <= targetSizeInBytes {
// UIImage
if let compressedImage = UIImage(data: imageData) {
print("✅ 图片压缩成功: \(dataSize) bytes (质量: \(quality))")
return compressedImage
}
}
}
}
if imageAspectRatio > viewAspectRatio {
//
scaledImageSize = CGSize(width: viewSize.height * imageAspectRatio, height: viewSize.height)
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)
// JPEG
print("⚠️ JPEG压缩后仍超过目标大小尝试缩小尺寸")
return compressImageByReducingSize(image, targetSizeInBytes: targetSizeInBytes)
}
//
let finalImageSize = CGSize(
width: scaledImageSize.width * scale,
height: scaledImageSize.height * scale
)
//
private func compressImageByReducingSize(_ image: UIImage, targetSizeInBytes: Int64) -> UIImage {
let originalSize = image.size
let originalWidth = originalSize.width
let originalHeight = originalSize.height
let finalImageOffset = CGPoint(
x: scaledImageOffset.x + offset.width,
y: scaledImageOffset.y + offset.height
)
//
let currentMemorySize = Int64(originalWidth * originalHeight * 4) // RGBA
//
let cropRect = CGRect(
x: -finalImageOffset.x,
y: -finalImageOffset.y,
width: finalImageSize.width,
height: finalImageSize.height
)
//
let scaleFactor = sqrt(Double(targetSizeInBytes) / Double(currentMemorySize))
let newWidth = max(originalWidth * CGFloat(scaleFactor), 40) // 40
let newHeight = max(originalHeight * CGFloat(scaleFactor), 40)
// -
let circlePath = UIBezierPath(ovalIn: CGRect(x: 0, y: 0, width: 80, height: 80))
circlePath.addClip()
let newSize = CGSize(width: newWidth, height: newHeight)
//
image.draw(in: cropRect)
}
}
//
let renderer = UIGraphicsImageRenderer(size: newSize)
let resizedImage = renderer.image { context in
image.draw(in: CGRect(origin: .zero, size: newSize))
}
// MARK: -
struct CropOverlay: View {
var body: some View {
GeometryReader { geometry in
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)
)
)
// JPEG
if let imageData = resizedImage.jpegData(compressionQuality: 0.3) {
let finalSize = Int64(imageData.count)
print("✅ 通过缩小尺寸压缩成功: \(finalSize) bytes (新尺寸: \(newWidth) x \(newHeight))")
//
Circle()
.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
)
if let finalImage = UIImage(data: imageData) {
return finalImage
}
}
//
ForEach(0..<4) { corner in
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